From db23d06fb265199d7a7c77b22ad0782945ea0039 Mon Sep 17 00:00:00 2001 From: araemer Date: Sat, 25 Oct 2025 18:10:56 +0200 Subject: [PATCH] Add StickyHeader Component and introduce clsx for merging tailwind classNames --- frontend/package-lock.json | 10 ++++++ frontend/package.json | 1 + frontend/src/App.css | 5 --- frontend/src/components/basics/Button.tsx | 8 ++++- frontend/src/components/basics/ButtonLink.tsx | 8 ++++- .../components/basics/CircularIconButton.tsx | 7 ++-- .../components/basics/MoveButtonControl.tsx | 16 +++++++-- .../components/basics/NumberStepControl.tsx | 5 ++- .../components/basics/NumberedListItem.tsx | 9 +++-- .../src/components/basics/PasswordField.tsx | 8 +++-- .../src/components/basics/SearchField.tsx | 16 ++++++--- .../src/components/basics/StickyHeader.tsx | 36 +++++++++++++++++++ .../components/recipes/RecipeDetailPage.tsx | 5 +-- .../src/components/recipes/RecipeListPage.tsx | 5 +-- 14 files changed, 115 insertions(+), 24 deletions(-) create mode 100644 frontend/src/components/basics/StickyHeader.tsx diff --git a/frontend/package-lock.json b/frontend/package-lock.json index a008f21..4c508f4 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -11,6 +11,7 @@ "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", + "clsx": "^2.1.1", "lucide": "^0.545.0", "lucide-react": "^0.545.0", "react": "^19.1.1", @@ -2345,6 +2346,15 @@ "node": ">=18" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 6a0c46a..9d943d6 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -13,6 +13,7 @@ "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", + "clsx": "^2.1.1", "lucide": "^0.545.0", "lucide-react": "^0.545.0", "react": "^19.1.1", diff --git a/frontend/src/App.css b/frontend/src/App.css index 7b01726..4473cb9 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -48,11 +48,6 @@ } /* @todo replace by components! - /* headings */ - .sticky-header { - @apply sticky bg-gray-100 top-0 left-0 right-0 pb-6 border-b-2 border-gray-300; - } - /* groups */ .button-group { @apply flex gap-4 mt-8; diff --git a/frontend/src/components/basics/Button.tsx b/frontend/src/components/basics/Button.tsx index 0ba947c..18efacf 100644 --- a/frontend/src/components/basics/Button.tsx +++ b/frontend/src/components/basics/Button.tsx @@ -1,5 +1,6 @@ import {defaultIconSize} from "./SvgIcon"; import {type BasicButtonProps, basicButtonStyle, ButtonType} from "./BasicButtonDefinitions"; +import clsx from "clsx"; type ButtonProps = BasicButtonProps & { onClick: () => void; @@ -20,7 +21,12 @@ export default function Button({ }: ButtonProps) { return ( {/* Left icon: Looking glass */} -
+
diff --git a/frontend/src/components/basics/StickyHeader.tsx b/frontend/src/components/basics/StickyHeader.tsx new file mode 100644 index 0000000..39d6536 --- /dev/null +++ b/frontend/src/components/basics/StickyHeader.tsx @@ -0,0 +1,36 @@ +import type {ReactNode} from "react"; +import clsx from "clsx"; + +/** + * A reusable sticky header component that stays fixed at the top + * of its parent container. You can pass arbitrary children such as + * headings, toolbars, or filters. + * + * Example: + * ```tsx + * + *

Recipes

+ * + *
+ * ``` + */ +type StickyHeaderProps = { + /** Content to render inside the header */ + children: ReactNode; + + /** Optional additional Tailwind classes */ + className?: string; +}; + +export default function StickyHeader({children, className = ""}: StickyHeaderProps) { + return ( +
+ {children} +
+ ); +} diff --git a/frontend/src/components/recipes/RecipeDetailPage.tsx b/frontend/src/components/recipes/RecipeDetailPage.tsx index 2b69677..bcea5cf 100644 --- a/frontend/src/components/recipes/RecipeDetailPage.tsx +++ b/frontend/src/components/recipes/RecipeDetailPage.tsx @@ -8,6 +8,7 @@ import {mapRecipeDtoToModel} from "../../mappers/RecipeMapper" import {NumberStepControl} from "../basics/NumberStepControl.tsx"; import {NumberedListItem} from "../basics/NumberedListItem.tsx"; import {ButtonType} from "../basics/BasicButtonDefinitions.ts"; +import StickyHeader from "../basics/StickyHeader.tsx"; /** @@ -81,9 +82,9 @@ export default function RecipeDetailPage() { {/* Container defining the maximum width of the content */}
{/* Header - remains in position when scrolling */} -
+

{recipeWorkingCopy.title}

-
+ {/* Content */}
diff --git a/frontend/src/components/recipes/RecipeListPage.tsx b/frontend/src/components/recipes/RecipeListPage.tsx index b813be9..79bfc72 100644 --- a/frontend/src/components/recipes/RecipeListPage.tsx +++ b/frontend/src/components/recipes/RecipeListPage.tsx @@ -5,6 +5,7 @@ import {fetchRecipeList} from "../../api/points/CompactRecipePoint" import {useNavigate} from "react-router-dom" import {getRecipeAddUrl, getRecipeDetailUrl, getRecipeListUrl} from "../../routes" import RecipeListToolbar from "./RecipeListToolbar" +import StickyHeader from "../basics/StickyHeader.tsx"; /** * Displays a list of recipes in a sidebar layout. @@ -45,14 +46,14 @@ export default function RecipeListPage() { {/* Container defining the maximum width of the content */}
{/* Header - remains in position when scrolling */} -
+

Recipes

-
+ {/*Content - List of recipe cards */}