Compare commits
No commits in common. "5bbd01480f8b563b99d7f23f254e78490158506c" and "635bec7f641461122c96f64eaa9a360f228770e9" have entirely different histories.
5bbd01480f
...
635bec7f64
5 changed files with 50 additions and 87 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import SvgIcon, { Icon } from "./SvgIcon"
|
|
||||||
/**
|
/**
|
||||||
* Custom search field component including a clear search functionality
|
* Custom search field component including a clear search functionality
|
||||||
*/
|
*/
|
||||||
|
|
@ -37,18 +37,40 @@ export default function SearchField({onSearchStringChanged} : SearchFieldProps){
|
||||||
Clears search string on click
|
Clears search string on click
|
||||||
*/}
|
*/}
|
||||||
<button
|
<button
|
||||||
className="absolute right-0 inset-y-0 flex items-center -ml-1 mr-3"
|
className="absolute right-0 inset-y-0 flex items-center"
|
||||||
onClick = { () => changeSearchString("") }
|
onClick = { () => changeSearchString("") }
|
||||||
>
|
>
|
||||||
<SvgIcon
|
<svg
|
||||||
icon = {Icon.X}
|
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>
|
</button>
|
||||||
{/* Left icon: Looking glass */}
|
{/* Left icon: Looking glass */}
|
||||||
<div className="absolute left-0 inset-y-0 flex items-center ml-3">
|
<div className="absolute left-0 inset-y-0 flex items-center">
|
||||||
<SvgIcon
|
<svg
|
||||||
icon = {Icon.LookingGlass}
|
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>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
/**
|
|
||||||
* SVG Icon component
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enum-like const object+type definition to define icons.
|
|
||||||
* The string corresponds to the path definition of the icon
|
|
||||||
*/
|
|
||||||
export const Icon = {
|
|
||||||
LookingGlass: "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z",
|
|
||||||
X: "M6 18L18 6M6 6l12 12"
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export type Icon = typeof Icon[keyof typeof Icon];
|
|
||||||
|
|
||||||
type SvgIconProps = {
|
|
||||||
pathDefinition : string
|
|
||||||
icon : Icon
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function SvgIcon({icon} : SvgIconProps){
|
|
||||||
return (
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
className="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={icon}
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -94,7 +94,7 @@ export default function RecipeEditor({ recipe, onSave, onCancel }: RecipeEditorP
|
||||||
placeholder="1"
|
placeholder="1"
|
||||||
value={draft.servings.amount}
|
value={draft.servings.amount}
|
||||||
onChange={e => {
|
onChange={e => {
|
||||||
const tempServings = draft.servings
|
let tempServings = draft.servings
|
||||||
tempServings.amount = Number(e.target.value)
|
tempServings.amount = Number(e.target.value)
|
||||||
setDraft({...draft, servings: tempServings})
|
setDraft({...draft, servings: tempServings})
|
||||||
}}
|
}}
|
||||||
|
|
@ -104,7 +104,7 @@ export default function RecipeEditor({ recipe, onSave, onCancel }: RecipeEditorP
|
||||||
placeholder="Persons"
|
placeholder="Persons"
|
||||||
value={draft.servings.unit}
|
value={draft.servings.unit}
|
||||||
onChange={e => {
|
onChange={e => {
|
||||||
const tempServings = draft.servings
|
let tempServings = draft.servings
|
||||||
tempServings.unit = e.target.value
|
tempServings.unit = e.target.value
|
||||||
setDraft({...draft, servings: tempServings})
|
setDraft({...draft, servings: tempServings})
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ 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"
|
import SearchField from "../basics/SearchField"
|
||||||
import RecipeListToolbar from "./RecipeListToolbar"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays a list of recipes in a sidebar layout.
|
* Displays a list of recipes in a sidebar layout.
|
||||||
|
|
@ -45,11 +44,25 @@ export default function RecipeListPage() {
|
||||||
{/* Header - remains in position when scrolling */}
|
{/* Header - remains in position when scrolling */}
|
||||||
<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>
|
||||||
<RecipeListToolbar
|
<div className="flex flex-wrap items-center gap-2 w-full">
|
||||||
onAddClicked={handleAdd}
|
{/* Label: left-aligned on medium+ screens, full-width on small screens */}
|
||||||
onSearchStringChanged={setSearchString}
|
<div className="order-2 md:order-1 w-full md:w-auto md:flex-1">
|
||||||
numberOfRecipes={recipeList.length}
|
<label className="label">{recipeList.length} Recipes</label>
|
||||||
/>
|
</div>
|
||||||
|
|
||||||
|
{/* Search + Add button container: right-aligned on medium+ screens */}
|
||||||
|
<div className="order-1 md:order-2 flex flex-1 md:flex-none justify-end gap-2 min-w-[160px]">
|
||||||
|
<div className="flex-1 md:flex-none md:max-w-[500px]">
|
||||||
|
<SearchField onSearchStringChanged={setSearchString} />
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
className="primary-button flex-shrink-0"
|
||||||
|
onClick={handleAdd}
|
||||||
|
>
|
||||||
|
Add recipe
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/*Content - List of recipe cards */}
|
{/*Content - List of recipe cards */}
|
||||||
<div className="w-full pt-4">
|
<div className="w-full pt-4">
|
||||||
|
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
import SearchField from "../basics/SearchField"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Toolbar for RecipeListPage containing searchfield, add recipe button and number of recipes
|
|
||||||
*/
|
|
||||||
type RecepeListToolbarProps = {
|
|
||||||
onSearchStringChanged: (searchString : string) => void
|
|
||||||
onAddClicked: () => void
|
|
||||||
numberOfRecipes : number
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function RecipeListToolbar({onSearchStringChanged, onAddClicked, numberOfRecipes} : RecepeListToolbarProps){
|
|
||||||
return (
|
|
||||||
<div className="flex flex-wrap items-center gap-2 w-full">
|
|
||||||
{/* Label: left-aligned on medium+ screens, full-width on small screens */}
|
|
||||||
<div className="order-2 md:order-1 w-full md:w-auto md:flex-1">
|
|
||||||
<label className="label">{numberOfRecipes} Recipes</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Search + Add button container: right-aligned on medium+ screens */}
|
|
||||||
<div className="order-1 md:order-2 flex flex-1 md:flex-none justify-end gap-2 min-w-[160px]">
|
|
||||||
<div className="flex-1 md:flex-none md:max-w-[500px]">
|
|
||||||
<SearchField onSearchStringChanged={onSearchStringChanged} />
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
className="primary-button flex-shrink-0"
|
|
||||||
onClick={onAddClicked}
|
|
||||||
>
|
|
||||||
Add recipe
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue