diff --git a/frontend/src/App.css b/frontend/src/App.css index 0a1233c..4ba5a12 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -1,102 +1,115 @@ /* Import Tailwind layers */ -@import "tailwindcss"; +@import "tailwindcss"; + @tailwind utilities; /* Custom recipe app styles */ -@layer components{ +@layer components { - /* background */ - .app-bg { - @apply flex items-center w-screen justify-center min-h-screen bg-gray-50; - } + /* background */ + .app-bg { + @apply flex items-center w-screen justify-center min-h-screen bg-gray-50; + } - .content-container { - @apply bg-gray-100 w-full min-h-screen max-w-6xl shadow-xl p-8 - } + .content-container { + @apply bg-gray-100 w-full min-h-screen max-w-6xl shadow-xl p-8; + } - /* headings */ - .content-title{ - @apply text-3xl font-black mb-8 text-blue-900; - } + /* headings */ + .sticky-header { + @apply sticky bg-gray-100 top-0 left-0 right-0 pb-6 border-b-2 border-gray-300; + } - .section-heading { - @apply text-xl font-bold mb-2; - } + .content-title { + @apply text-3xl font-black mb-8 text-blue-900; + } - .subsection-heading { - @apply font-semibold mb-2 mt-4; - } + .section-heading { + @apply text-xl font-bold mb-2; + } - /* icons */ - .default-icon { - @apply text-gray-400 hover:text-gray-500; - } + .subsection-heading { + @apply font-semibold mb-2 mt-4; + } + + /* icons */ + .default-icon { + @apply text-gray-400 hover:text-gray-500; + } - /* labels */ - .label { - @apply text-gray-600; - } + /* labels */ + .label { + @apply text-gray-600; + } - /* errors */ - .error-text { - @apply text-sm text-red-600; - } + /* errors */ + .error-text { + @apply text-sm text-red-600; + } - /* buttons */ - .basic-button{ - @apply px-4 py-2 shadow-md rounded-lg whitespace-nowrap; - } - .default-button-bg { - @apply bg-gray-300 hover:bg-gray-400; - } - .default-button-text{ - @apply text-gray-600; - } - .primary-button-bg { - @apply bg-blue-300 hover:bg-blue-400; - } - .primary-button-text { - @apply text-gray-600; - } - .dark-button-bg{ - @apply bg-gray-600 hover:bg-gray-800; - } - .dark-button-text{ - @apply text-white; - } - .transparent-button-bg { - @apply bg-transparent hover:bg-transparent; - } - .transparent-button-text { - @apply text-gray-600; - } + /* buttons */ + .basic-button { + @apply px-4 py-2 shadow-md rounded-lg whitespace-nowrap; + } - /* input fields like input and textarea */ - .input-field { - @apply p-2 w-full border rounded-md bg-white placeholder-gray-400 border-gray-600 hover:border-blue-800 transition-colors text-gray-600 focus:outline-none focus:border-blue-900; - } + .default-button-bg { + @apply bg-gray-300 hover:bg-gray-400; + } - /* groups */ - .button-group{ - @apply flex gap-4 mt-4; - } + .default-button-text { + @apply text-gray-600; + } - .horizontal-input-group{ - @apply flex gap-2 mb-2 items-center; - } + .primary-button-bg { + @apply bg-blue-300 hover:bg-blue-400; + } - /* lists */ - .default-list { - @apply list-disc pl-6 mb-6; - } + .primary-button-text { + @apply text-gray-600; + } - .ingredient-group-card { - @apply py-4 border-b border-gray-400; - } + .dark-button-bg { + @apply bg-gray-600 hover:bg-gray-800; + } + + .dark-button-text { + @apply text-white; + } + + .transparent-button-bg { + @apply bg-transparent hover:bg-transparent; + } + + .transparent-button-text { + @apply text-gray-600; + } + + /* input fields like input and textarea */ + .input-field { + @apply p-2 w-full border rounded-md bg-white placeholder-gray-400 border-gray-600 hover:border-blue-800 transition-colors text-gray-600 focus:outline-none focus:border-blue-900; + } + + /* groups */ + .button-group { + @apply flex gap-4 mt-4; + } + + .horizontal-input-group { + @apply flex gap-2 mb-2 items-center; + } + + /* lists */ + .default-list { + @apply list-disc pl-6 mb-6; + } + + .ingredient-group-card { + @apply py-4 border-b border-gray-400; + } + + .enumeration-indicator { + @apply flex-shrink-0 w-7 h-7 rounded-full bg-blue-300 text-white flex items-center justify-center shadow-sm; + } - .enumeration-indicator{ - @apply flex-shrink-0 w-7 h-7 rounded-full bg-blue-300 text-white flex items-center justify-center shadow-sm; - } - } \ No newline at end of file diff --git a/frontend/src/components/recipes/RecipeDetailPage.tsx b/frontend/src/components/recipes/RecipeDetailPage.tsx index 4cf8eb1..a1fcfbf 100644 --- a/frontend/src/components/recipes/RecipeDetailPage.tsx +++ b/frontend/src/components/recipes/RecipeDetailPage.tsx @@ -77,7 +77,7 @@ export default function RecipeDetailPage() { {/* Container defining the maximum width of the content */}
{/* Header - remains in position when scrolling */} -
+

{recipeWorkingCopy.title}

@@ -93,19 +93,44 @@ export default function RecipeDetailPage() { )} {/* Servings */} -
-

For {recipeWorkingCopy.servings.amount} {recipeWorkingCopy.servings.unit}

- { - recalculateIngredients(Number(e.target.value)) - } - } - /> +
+

+ Für {recipeWorkingCopy.servings.amount} {recipeWorkingCopy.servings.unit} +

+ +
+ {/* Minus button */} + + + {/* Number input (no spin buttons) */} + recalculateIngredients(Number(e.target.value))} + min={1} + /> + + {/* Plus button */} + +
+ {/* Ingredients */}

Zutaten

    diff --git a/frontend/src/components/recipes/RecipeListPage.tsx b/frontend/src/components/recipes/RecipeListPage.tsx index ab1ea5f..090ee8a 100644 --- a/frontend/src/components/recipes/RecipeListPage.tsx +++ b/frontend/src/components/recipes/RecipeListPage.tsx @@ -1,9 +1,9 @@ -import { useEffect, useState } from "react" +import {useEffect, useState} from "react" import RecipeListItem from "./RecipeListItem" -import type { RecipeModel } from "../../models/RecipeModel" -import { fetchRecipeList } from "../../api/points/CompactRecipePoint" -import { useNavigate } from "react-router-dom" -import { getRecipeAddUrl, getRecipeDetailUrl, getRecipeListUrl } from "../../routes" +import type {RecipeModel} from "../../models/RecipeModel" +import {fetchRecipeList} from "../../api/points/CompactRecipePoint" +import {useNavigate} from "react-router-dom" +import {getRecipeAddUrl, getRecipeDetailUrl, getRecipeListUrl} from "../../routes" import RecipeListToolbar from "./RecipeListToolbar" /** @@ -12,58 +12,61 @@ import RecipeListToolbar from "./RecipeListToolbar" */ export default function RecipeListPage() { - const navigate = useNavigate() - const [recipeList, setRecipeList] = useState(null) - const [searchString, setSearchString] = useState("") - // load recipes once on render and whenever search string changes - // @todo add delay. Only reload list if the search string hasn't changed for ~200 ms + const navigate = useNavigate() + const [recipeList, setRecipeList] = useState(null) + const [searchString, setSearchString] = useState("") + // load recipes once on render and whenever search string changes + // @todo add delay. Only reload list if the search string hasn't changed for ~200 ms useEffect(() => { - console.log("loading recipe list with searchString", searchString) - const loadRecipeList = async () => { - try { - // Fetch recipe list - const data = await fetchRecipeList(searchString) - // @todo add and use compact recipe mapper - setRecipeList(data) - } catch (err) { - console.error(err) - } - } - loadRecipeList() - }, [,searchString]) - - const handleAdd = () => { - navigate(getRecipeAddUrl()) - } + console.log("loading recipe list with searchString", searchString) + const loadRecipeList = async () => { + try { + // Fetch recipe list + const data = await fetchRecipeList(searchString) + // @todo add and use compact recipe mapper + setRecipeList(data) + } catch (err) { + console.error(err) + } + } + loadRecipeList() + }, [, searchString]) - if(!recipeList) { return
    Loading!
    } - return ( - /*Container spanning entire screen used to center content horizontally */ -
    - {/* Container defining the maximum width of the content */} -
    - {/* Header - remains in position when scrolling */} -
    -

    Recipes

    - + const handleAdd = () => { + navigate(getRecipeAddUrl()) + } + + if (!recipeList) { + return
    Loading!
    + } + return ( + /*Container spanning entire screen used to center content horizontally */ +
    + {/* Container defining the maximum width of the content */} +
    + {/* Header - remains in position when scrolling */} +
    +

    Recipes

    + +
    + {/*Content - List of recipe cards */} +
    +
    + {recipeList.map((recipe) => ( + + ))} +
    +
    +
    - {/*Content - List of recipe cards */} -
    -
    - {recipeList.map((recipe) => ( - - ))} -
    -
    -
    -
    - ) + ) } diff --git a/frontend/src/index.css b/frontend/src/index.css index fc6b67f..48c6fa2 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -1,29 +1,42 @@ @import "tailwindcss"; -@import "tailwindcss/utilities"; +@import "tailwindcss/utilities"; :root { - font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; + font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; } +/* Hide number input spinners (Chrome, Safari, Edge, Opera) */ +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + -webkit-appearance: none; + margin: 0; +} + +/* Hide number input spinners in Firefox */ +input[type="number"] { + -moz-appearance: textfield; +} + + /*a { font-weight: 500; color: #646cff;