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:
Frank Noirot
2024-08-06 16:19:30 -04:00
committed by GitHub
parent c843dfad95
commit 77b565f781
2 changed files with 74 additions and 5 deletions

View 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>
)
}

View File

@ -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>
)} )}
</> </>