add custom search field component

This commit is contained in:
Anika Raemer 2025-09-14 14:10:46 +02:00
parent df406b636e
commit 30c138d13f
4 changed files with 84 additions and 9 deletions

View file

@ -43,7 +43,7 @@
/* input field */ /* input field */
.input-field { .input-field {
@apply border p-2 w-full rounded placeholder-gray-400; @apply p-2 w-full border rounded-md placeholder-gray-400 border-gray-600 hover:border-blue-600 transition-colors text-gray-600 focus:outline-none focus:border-blue-800;
} }
.text-area { .text-area {

View file

@ -0,0 +1,73 @@
/**
* Custom search field component including a clear search functionality
*/
type SearchFieldProps = {
searchString : string
onSearchStringChanged: (searchString : string) => void
}
/**
* @param SearchFieldProps consisting of an initial searchString and an onSearchStringChanged handler
* @returns Custom search field component
*/
export default function SearchField({searchString, onSearchStringChanged} : SearchFieldProps){
const setSearchString = (newSearchString : string) => {
searchString = newSearchString;
onSearchStringChanged(searchString)
}
return (
<div className="relative">
{/* Input of searchfield
Defines border and behavior. Requires extra padding at both sides to
accomodate the icons
*/}
<input
className="input-field pl-10 pr-10"
type="text"
placeholder="Search"
value={searchString}
onChange={ e => setSearchString(e.target.value) }
/>
{/* Right icon: X
Clears search string on click
*/}
<button
className="absolute right-0 inset-y-0 flex items-center"
onClick = { () => setSearchString("") }
>
<svg
xmlns="http://www.w3.org/2000/svg"
className="-ml-1 mr-3 h-6 w-6 text-gray-400 hover:text-gray-500"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
{/* Left icon: Looking glass */}
<div className="absolute left-0 inset-y-0 flex items-center">
<svg
xmlns="http://www.w3.org/2000/svg"
className="ml-3 h-6 w-6 text-gray-400 hover:text-gray-500"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
/>
</svg>
</div>
</div>
)
}

View file

@ -121,7 +121,7 @@ export default function RecipeEditor({ recipe, onSave, onCancel }: RecipeEditorP
<h3 className="subsection-heading">Instructions</h3> <h3 className="subsection-heading">Instructions</h3>
{/* Instructions */} {/* Instructions */}
<textarea <textarea
className="text-area" className="input-field"
placeholder="Instructions" placeholder="Instructions"
value={draft.instructions} value={draft.instructions}
onChange={e => setDraft({ ...draft, instructions: e.target.value })} onChange={e => setDraft({ ...draft, instructions: e.target.value })}

View file

@ -4,6 +4,7 @@ import type { Recipe } from "../../types/recipe"
import { fetchRecipeList } from "../../api/recipePoint" import { fetchRecipeList } from "../../api/recipePoint"
import { useNavigate } from "react-router-dom" import { useNavigate } from "react-router-dom"
import { getRecipeAddUrl, getRecipeDetailUrl } from "../../routes" import { getRecipeAddUrl, getRecipeDetailUrl } from "../../routes"
import SearchField from "../basics/SearchField"
/** /**
* Displays a list of recipes in a sidebar layout. * Displays a list of recipes in a sidebar layout.
@ -36,7 +37,7 @@ export default function RecipeListPage() {
if(!recipeList) { return <div>Unable to load recipes!</div>} if(!recipeList) { return <div>Unable to load recipes!</div>}
return ( return (
/*Contaier spanning entire screen used to center content horizontally */ /*Container spanning entire screen used to center content horizontally */
<div className="w-screen min-h-screen flex justify-center"> <div className="w-screen min-h-screen flex justify-center">
{/* Container defining the maximum width of the content */} {/* Container defining the maximum width of the content */}
<div className="bg-gray-100 w-full min-h-screen max-w-5xl shadow-xl p-8"> <div className="bg-gray-100 w-full min-h-screen max-w-5xl shadow-xl p-8">
@ -44,13 +45,14 @@ export default function RecipeListPage() {
<div className="sticky bg-gray-100 top-0 left-0 right-0 pb-4 border-b-2 border-gray-300"> <div className="sticky bg-gray-100 top-0 left-0 right-0 pb-4 border-b-2 border-gray-300">
<h1 className="content-title text-blue-900">Recipes</h1> <h1 className="content-title text-blue-900">Recipes</h1>
<div className="flex columns-4 content-stretch gap-2 items-center"> <div className="flex columns-4 content-stretch gap-2 items-center">
{/* Number of recipes inlist */}
<label className="text-gray-500 w-2/3">{recipeList.length} Recipes</label> <label className="text-gray-500 w-2/3">{recipeList.length} Recipes</label>
<input className="input-field" {/* Search field */}
placeholder="Search" <SearchField
onChange={e => { searchString=""
setSearchString(e.target.value) onSearchStringChanged={setSearchString}
}} />
></input> {/* Add recipe button */}
<button className="primary-button" <button className="primary-button"
onClick={handleAdd} onClick={handleAdd}
> >