Move Omnisearch select component to the tailwind-ui combobox

This commit is contained in:
Joshua Coles 2023-10-13 21:19:56 +01:00
parent de6af8c6b5
commit 87e482e943
3 changed files with 83 additions and 20 deletions

BIN
bun.lockb

Binary file not shown.

View File

@ -14,6 +14,7 @@
"@heroicons/react": "^2.0.18", "@heroicons/react": "^2.0.18",
"@tailwindcss/forms": "^0.5.6", "@tailwindcss/forms": "^0.5.6",
"@tanstack/react-query": "^4.35.3", "@tanstack/react-query": "^4.35.3",
"classnames": "^2.3.2",
"ramda": "^0.29.0", "ramda": "^0.29.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",

View File

@ -1,12 +1,14 @@
import {ChangeEvent, ReactNode, useCallback, useState} from "react"; import {ReactNode, useCallback, useState} from "react";
import {FixedSizeList as List} from "react-window"; import {FixedSizeList as List} from "react-window";
import Select, {createFilter, MenuListProps} from "react-select"; import Select, {createFilter, MenuListProps} from "react-select";
import * as R from 'ramda'; import * as R from 'ramda';
import classNames from "classnames";
import {useQuery} from "@tanstack/react-query"; import {useQuery} from "@tanstack/react-query";
import {LinkCollection, Option} from "./aliases"; import {LinkCollection, type Option} from "./aliases";
import AsyncSelect from "react-select/async";
import {CheckIcon, ChevronUpDownIcon} from '@heroicons/react/20/solid'
import {Combobox} from '@headlessui/react'
// TODO: Fix this for wrapping items, esp on phones // TODO: Fix this for wrapping items, esp on phones
const height = 35; const height = 35;
@ -82,6 +84,7 @@ type ResultNoteApi = {
basename: string basename: string
foundWords: string[] foundWords: string[]
matches: SearchMatchApi[] matches: SearchMatchApi[]
excerpt: string
} }
type SearchMatchApi = { type SearchMatchApi = {
@ -89,7 +92,9 @@ type SearchMatchApi = {
offset: number offset: number
} }
export function OmnisearchSelect({ setSelected }: { setSelected: (value: Option) => void }) { export function OmnisearchSelect({setSelected}: {
setSelected: (value: Option) => void
}) {
const { const {
data: metadata, data: metadata,
isLoading, isLoading,
@ -108,30 +113,87 @@ export function OmnisearchSelect({ setSelected }: { setSelected: (value: Option)
}, },
}); });
const [filter, setFilter] = useState('[]'); const [query, setQuery] = useState('')
const [selectedPerson, setSelectedPerson] = useState(null);
const onChange = useCallback((e: ChangeEvent<HTMLInputElement>) => { const {data: filteredPeople} = useQuery({
setFilter(e.target.value); queryKey: ['obsidian-omnisearch', query],
}, []);
const { data: options } = useQuery({
queryKey: ['obsidian-omnisearch', filter],
initialData: [], initialData: [],
queryFn: async () => { queryFn: async () => {
const response = await fetch(`/search?q=${filter}`) if (query === '') {
const fullData: ResultNoteApi[] = await response.json(); return [];
} else {
const response = await fetch(`/search?q=${query}`)
const fullData: ResultNoteApi[] = await response.json();
return fullData; return fullData;
}
} }
}); });
return (<> return (<Combobox as="div" value={selectedPerson} onChange={setSelectedPerson}>
<input onChange={onChange}/> <Combobox.Label className="block text-sm font-medium leading-6 text-gray-900">Assigned to</Combobox.Label>
<div className="relative mt-2">
<Combobox.Input
className="w-full rounded-md border-0 bg-white py-1.5 pl-3 pr-12 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
onChange={(event) => setQuery(event.target.value)}
displayValue={(person: ResultNoteApi) => person?.basename}
/>
<Combobox.Button
className="absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none">
<ChevronUpDownIcon className="h-5 w-5 text-gray-400" aria-hidden="true"/>
</Combobox.Button>
{options.map(option => ( {filteredPeople.length > 0 && (
<div>{option.basename}</div> <Combobox.Options
))} className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
</>); {filteredPeople.map((person) => (
<Combobox.Option
key={person.path}
value={person}
className={({active}) =>
classNames(
'relative cursor-default select-none py-2 pl-3 pr-9',
active ? 'bg-indigo-600 text-white' : 'text-gray-900'
)
}
>
{({
active,
selected
}) => (
<>
<div className="flex">
<span
className={classNames('truncate', selected && 'font-semibold')}>{person?.basename}</span>
<span
className={classNames(
'ml-2 truncate text-gray-500',
active ? 'text-indigo-200' : 'text-gray-500'
)}
>
{person?.path}
</span>
</div>
{selected && (
<span
className={classNames(
'absolute inset-y-0 right-0 flex items-center pr-4',
active ? 'text-white' : 'text-indigo-600'
)}
>
<CheckIcon className="h-5 w-5" aria-hidden="true"/>
</span>
)}
</>
)}
</Combobox.Option>
))}
</Combobox.Options>
)}
</div>
</Combobox>);
} }
export function App() { export function App() {