Extract NumberStepControl
This commit is contained in:
parent
f980d4d86d
commit
13fe0ee852
4 changed files with 134 additions and 83 deletions
|
|
@ -108,7 +108,7 @@
|
|||
@apply py-4 border-b border-gray-400;
|
||||
}
|
||||
|
||||
.enumeration-indicator {
|
||||
.circular-container {
|
||||
@apply flex-shrink-0 w-7 h-7 rounded-full bg-blue-300 text-white flex items-center justify-center shadow-sm;
|
||||
}
|
||||
|
||||
|
|
|
|||
74
frontend/src/components/basics/NumberStepControl.tsx
Normal file
74
frontend/src/components/basics/NumberStepControl.tsx
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
import {Minus, Plus} from "lucide-react";
|
||||
import {defaultIconSize} from "./SvgIcon.tsx";
|
||||
|
||||
type NumberStepControlProps = {
|
||||
/** Current numeric value */
|
||||
value: number;
|
||||
/** Callback when value changes */
|
||||
onChange: (newValue: number) => void;
|
||||
/** Optional: minimum allowed value */
|
||||
min?: number;
|
||||
/** Optional: maximum allowed value */
|
||||
max?: number;
|
||||
/** Optional: step increment (default = 1) */
|
||||
step?: number;
|
||||
/** Optional: additional Tailwind classes */
|
||||
className?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* A compact number input with +/– buttons.
|
||||
* Removes native browser spinners and supports min/max limits.
|
||||
*/
|
||||
export function NumberStepControl({
|
||||
value,
|
||||
onChange,
|
||||
min = Number.NEGATIVE_INFINITY,
|
||||
max = Number.POSITIVE_INFINITY,
|
||||
step = 1,
|
||||
className = "",
|
||||
}: NumberStepControlProps) {
|
||||
const handleDecrease = () => {
|
||||
const newValue = Math.max(value - step, min);
|
||||
onChange(newValue);
|
||||
};
|
||||
|
||||
const handleIncrease = () => {
|
||||
const newValue = Math.min(value + step, max);
|
||||
onChange(newValue);
|
||||
};
|
||||
|
||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const num = Number(e.target.value);
|
||||
if (!isNaN(num)) onChange(Math.min(Math.max(num, min), max));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`flex items-center gap-2 ${className}`}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleDecrease}
|
||||
className="circular-container primary-button-bg"
|
||||
>
|
||||
<Minus size={defaultIconSize}/>
|
||||
</button>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
inputMode="numeric"
|
||||
pattern="[0-9]*"
|
||||
value={value}
|
||||
onChange={handleInputChange}
|
||||
className="w-16 text-center input-field"
|
||||
/>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleIncrease}
|
||||
className="circular-container primary-button-bg"
|
||||
>
|
||||
<Plus size={defaultIconSize}/>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -37,7 +37,7 @@ export function InstructionStepDesktopListItem({
|
|||
<div
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
className="enumeration-indicator cursor-grab"
|
||||
className="circular-container cursor-grab"
|
||||
title="Ziehen, um neu zu ordnen"
|
||||
>
|
||||
{index + 1}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import {fetchRecipe} from "../../api/points/RecipePoint"
|
|||
import {getRecipeEditUrl, getRecipeListUrl} from "../../routes"
|
||||
import ButtonLink from "../basics/ButtonLink"
|
||||
import {mapRecipeDtoToModel} from "../../mappers/RecipeMapper"
|
||||
import {NumberStepControl} from "../basics/NumberStepControl.tsx";
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -99,36 +100,12 @@ export default function RecipeDetailPage() {
|
|||
Für {recipeWorkingCopy.servings.amount} {recipeWorkingCopy.servings.unit}
|
||||
</p>
|
||||
|
||||
<div className="flex items-center justify-end sm:justify-center gap-2">
|
||||
{/* Minus button */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => recalculateIngredients(Math.max(1, recipeWorkingCopy.servings.amount - 1))}
|
||||
className="enumeration-indicator primary-button-bg"
|
||||
>
|
||||
–
|
||||
</button>
|
||||
|
||||
{/* Number input (no spin buttons) */}
|
||||
<input
|
||||
type="number"
|
||||
inputMode="numeric"
|
||||
pattern="[0-9]*"
|
||||
className="w-16 text-center input-field"
|
||||
<NumberStepControl
|
||||
value={recipeWorkingCopy.servings.amount}
|
||||
onChange={e => recalculateIngredients(Number(e.target.value))}
|
||||
onChange={recalculateIngredients}
|
||||
min={1}
|
||||
className="justify-end sm:justify-center"
|
||||
/>
|
||||
|
||||
{/* Plus button */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => recalculateIngredients(recipeWorkingCopy.servings.amount + 1)}
|
||||
className="enumeration-indicator primary-button-bg"
|
||||
>
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Ingredients */}
|
||||
|
|
@ -156,7 +133,7 @@ export default function RecipeDetailPage() {
|
|||
{recipe.instructionStepList.map((step, j) => (
|
||||
<li key={j} className="flex items-start gap-4">
|
||||
{/* Step number circle */}
|
||||
<div className="enumeration-indicator">
|
||||
<div className="circular-container">
|
||||
{j + 1}
|
||||
</div>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue