add custom search field component
This commit is contained in:
parent
df406b636e
commit
30c138d13f
4 changed files with 84 additions and 9 deletions
|
|
@ -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 {
|
||||||
|
|
|
||||||
73
frontend/src/components/basics/SearchField.tsx
Normal file
73
frontend/src/components/basics/SearchField.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -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 })}
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue