Add a search bar to the projects/home page (#3301)
* Add a search bar to the projects/home page * Better hotkey config * Look at this (photo)Graph *in the voice of Nickelback* * Re-run CI * Look at this (photo)Graph *in the voice of Nickelback* * Re-run CI --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
63
src/components/ProjectSearchBar.tsx
Normal file
63
src/components/ProjectSearchBar.tsx
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import { Project } from 'wasm-lib/kcl/bindings/Project'
|
||||||
|
import { CustomIcon } from './CustomIcon'
|
||||||
|
import { useEffect, useRef, useState } from 'react'
|
||||||
|
import { useHotkeys } from 'react-hotkeys-hook'
|
||||||
|
import Fuse from 'fuse.js'
|
||||||
|
|
||||||
|
export function useProjectSearch(projects: Project[]) {
|
||||||
|
const [query, setQuery] = useState('')
|
||||||
|
const [searchResults, setSearchResults] = useState(projects)
|
||||||
|
|
||||||
|
const fuse = new Fuse(projects, {
|
||||||
|
keys: [{ name: 'name', weight: 0.7 }],
|
||||||
|
includeScore: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const results = fuse.search(query).map((result) => result.item)
|
||||||
|
setSearchResults(query.length > 0 ? results : projects)
|
||||||
|
}, [query, projects])
|
||||||
|
|
||||||
|
return {
|
||||||
|
searchResults,
|
||||||
|
query,
|
||||||
|
setQuery,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ProjectSearchBar({
|
||||||
|
setQuery,
|
||||||
|
}: {
|
||||||
|
setQuery: (query: string) => void
|
||||||
|
}) {
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null)
|
||||||
|
useHotkeys(
|
||||||
|
'Ctrl+.',
|
||||||
|
(event) => {
|
||||||
|
event.preventDefault()
|
||||||
|
inputRef.current?.focus()
|
||||||
|
},
|
||||||
|
{ enableOnFormTags: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative group">
|
||||||
|
<div className="flex items-center gap-2 py-0.5 pl-0.5 pr-2 rounded border-solid border border-primary/10 dark:border-chalkboard-80 focus-within:border-primary dark:focus-within:border-chalkboard-30">
|
||||||
|
<CustomIcon
|
||||||
|
name="search"
|
||||||
|
className="w-5 h-5 rounded-sm bg-primary/10 dark:bg-transparent text-primary dark:text-chalkboard-10 group-focus-within:bg-primary group-focus-within:text-chalkboard-10"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
ref={inputRef}
|
||||||
|
onChange={(event) => setQuery(event.target.value)}
|
||||||
|
className="w-full text-sm bg-transparent focus:outline-none selection:bg-primary/20 dark:selection:bg-primary/40 dark:focus:outline-none"
|
||||||
|
placeholder="Search projects (^.)"
|
||||||
|
autoCapitalize="off"
|
||||||
|
autoComplete="off"
|
||||||
|
autoCorrect="off"
|
||||||
|
spellCheck="false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -39,6 +39,7 @@ import {
|
|||||||
listProjects,
|
listProjects,
|
||||||
renameProjectDirectory,
|
renameProjectDirectory,
|
||||||
} from 'lib/tauri'
|
} from 'lib/tauri'
|
||||||
|
import { ProjectSearchBar, useProjectSearch } from 'components/ProjectSearchBar'
|
||||||
|
|
||||||
// This route only opens in the Tauri desktop context for now,
|
// This route only opens in the Tauri desktop context for now,
|
||||||
// as defined in Router.tsx, so we can use the Tauri APIs and types.
|
// as defined in Router.tsx, so we can use the Tauri APIs and types.
|
||||||
@ -154,6 +155,7 @@ const Home = () => {
|
|||||||
})
|
})
|
||||||
const { projects } = state.context
|
const { projects } = state.context
|
||||||
const [searchParams, setSearchParams] = useSearchParams()
|
const [searchParams, setSearchParams] = useSearchParams()
|
||||||
|
const { searchResults, query, setQuery } = useProjectSearch(projects)
|
||||||
const sort = searchParams.get('sort_by') ?? 'modified:desc'
|
const sort = searchParams.get('sort_by') ?? 'modified:desc'
|
||||||
|
|
||||||
const isSortByModified = sort?.includes('modified') || !sort || sort === null
|
const isSortByModified = sort?.includes('modified') || !sort || sort === null
|
||||||
@ -206,8 +208,8 @@ const Home = () => {
|
|||||||
<AppHeader showToolbar={false} />
|
<AppHeader showToolbar={false} />
|
||||||
<div className="w-full flex flex-col overflow-hidden max-w-5xl px-4 mx-auto mt-24 lg:px-2">
|
<div className="w-full flex flex-col overflow-hidden max-w-5xl px-4 mx-auto mt-24 lg:px-2">
|
||||||
<section>
|
<section>
|
||||||
<div className="flex justify-between items-baseline select-none">
|
<div className="flex justify-between items-center select-none">
|
||||||
<div className="flex gap-8 items-baseline">
|
<div className="flex gap-8 items-center">
|
||||||
<h1 className="text-3xl font-bold">Your Projects</h1>
|
<h1 className="text-3xl font-bold">Your Projects</h1>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
@ -225,6 +227,7 @@ const Home = () => {
|
|||||||
</ActionButton>
|
</ActionButton>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
|
<ProjectSearchBar setQuery={setQuery} />
|
||||||
<small>Sort by</small>
|
<small>Sort by</small>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
@ -289,9 +292,9 @@ const Home = () => {
|
|||||||
<Loading>Loading your Projects...</Loading>
|
<Loading>Loading your Projects...</Loading>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{projects.length > 0 ? (
|
{searchResults.length > 0 ? (
|
||||||
<ul className="grid w-full grid-cols-4 gap-4">
|
<ul className="grid w-full grid-cols-4 gap-4">
|
||||||
{projects.sort(getSortFunction(sort)).map((project) => (
|
{searchResults.sort(getSortFunction(sort)).map((project) => (
|
||||||
<ProjectCard
|
<ProjectCard
|
||||||
key={project.name}
|
key={project.name}
|
||||||
project={project}
|
project={project}
|
||||||
@ -302,7 +305,10 @@ const Home = () => {
|
|||||||
</ul>
|
</ul>
|
||||||
) : (
|
) : (
|
||||||
<p className="p-4 my-8 border border-dashed rounded border-chalkboard-30 dark:border-chalkboard-70">
|
<p className="p-4 my-8 border border-dashed rounded border-chalkboard-30 dark:border-chalkboard-70">
|
||||||
No Projects found, ready to make your first one?
|
No Projects found
|
||||||
|
{projects.length === 0
|
||||||
|
? ', ready to make your first one?'
|
||||||
|
: ` with the search term "${query}"`}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
Reference in New Issue
Block a user