better handling of textbox focus for mobile

This commit is contained in:
fred 2025-08-21 10:07:16 -07:00
parent 798e879863
commit cfa0b9bc07
3 changed files with 102 additions and 22 deletions

View file

@ -12,7 +12,7 @@ class RecipeModel {
async getAllRecipes(): Promise<any[]> { async getAllRecipes(): Promise<any[]> {
try { try {
logger.info("index page view") logger.info("index page view");
return await this.prisma.recipes.findMany(); return await this.prisma.recipes.findMany();
} catch (err) { } catch (err) {
console.error("Error fetching all recipes:", err); console.error("Error fetching all recipes:", err);
@ -46,7 +46,10 @@ class RecipeModel {
instruction: step.instruction, instruction: step.instruction,
})), })),
}; };
logger.info("recipe page view", { recipe_id: data.details.id, recipe_name: data.details.name }) logger.info("recipe page view", {
recipe_id: data.details.id,
recipe_name: data.details.name,
});
return data; return data;
} catch (err) { } catch (err) {
console.log("Error finding recipe:", err); console.log("Error finding recipe:", err);

View file

@ -1,20 +1,23 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from "react";
interface AddBulkIngredientsProps { interface AddBulkIngredientsProps {
ingredients: string[]; ingredients: string[];
onChange?: (ingredients: string[]) => void; onChange?: (ingredients: string[]) => void;
} }
const AddBulkIngredients: React.FC<AddBulkIngredientsProps> = ({ ingredients, onChange }) => { const AddBulkIngredients: React.FC<AddBulkIngredientsProps> = ({
const [textValue, setTextValue] = useState<string>(''); ingredients,
onChange,
}) => {
const [textValue, setTextValue] = useState<string>("");
useEffect(() => { useEffect(() => {
const textRepresentation = ingredients.join('\n'); const textRepresentation = ingredients.join("\n");
setTextValue(textRepresentation); setTextValue(textRepresentation);
}, [ingredients]); }, [ingredients]);
const parseAndUpdate = (value: string) => { const parseAndUpdate = (value: string) => {
const lines = value.split('\n').filter(line => line.trim() !== ''); const lines = value.split("\n").filter((line) => line.trim() !== "");
if (onChange) onChange(lines); if (onChange) onChange(lines);
}; };
@ -24,26 +27,66 @@ const AddBulkIngredients: React.FC<AddBulkIngredientsProps> = ({ ingredients, on
}; };
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => { const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (e.key === 'Enter') { if (e.key === "Enter") {
parseAndUpdate(textValue); parseAndUpdate(textValue);
} }
}; };
const handleBlur = () => { const handleBlur = () => {
parseAndUpdate(textValue); parseAndUpdate(textValue);
document.body.style.overflow = "";
removeTouchListeners();
};
const manageOverflow = () => {
const lineCount = textValue
.split("\n")
.filter((line) => line.trim() !== "").length;
if (lineCount > 8) {
document.body.style.overflow = "hidden";
addTouchListeners();
} else {
document.body.style.overflow = "";
removeTouchListeners();
}
};
const handleFocus = () => {
manageOverflow();
};
const handleTouchEnd = () => {
manageOverflow();
};
const addTouchListeners = () => {
window.addEventListener("touchmove", preventDefault, { passive: false });
};
const removeTouchListeners = () => {
window.removeEventListener("touchmove", preventDefault);
};
const preventDefault = (e: TouchEvent) => {
e.preventDefault();
}; };
return ( return (
<div> <div>
<h3 className="text-xl font-bold text-[var(--color-secondaryTextDark)]">Ingredients:</h3> <h3 className="text-xl font-bold text-[var(--color-secondaryTextDark)]">
Ingredients:
</h3>
<textarea <textarea
rows={8} rows={8}
value={textValue} value={textValue}
onChange={handleInputChange} onChange={handleInputChange}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
onBlur={handleBlur} onBlur={handleBlur}
onFocus={handleFocus}
onTouchEnd={handleTouchEnd}
placeholder="Enter ingredients separated by new line" placeholder="Enter ingredients separated by new line"
className="mb-4 p-2 border border-[var(--color-primaryBorder)] rounded w-full" className="mb-4 p-2 border border-[var(--color-primaryBorder)] rounded w-full overflow-y-auto"
style={{ overflowY: "auto" }}
/> />
</div> </div>
); );

View file

@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect, useRef } from "react";
interface Step { interface Step {
step_number: number; step_number: number;
@ -11,12 +11,13 @@ interface AddBulkStepsProps {
} }
const AddBulkSteps: React.FC<AddBulkStepsProps> = ({ steps, onChange }) => { const AddBulkSteps: React.FC<AddBulkStepsProps> = ({ steps, onChange }) => {
const [textValue, setTextValue] = useState<string>(''); const [textValue, setTextValue] = useState<string>("");
const textareaRef = useRef<HTMLTextAreaElement | null>(null);
useEffect(() => { useEffect(() => {
const textRepresentation = steps.map(step => const textRepresentation = steps
`${step.instruction}` .map((step) => `${step.instruction}`)
).join('\n'); .join("\n");
setTextValue(textRepresentation); setTextValue(textRepresentation);
}, [steps]); }, [steps]);
@ -24,38 +25,71 @@ const AddBulkSteps: React.FC<AddBulkStepsProps> = ({ steps, onChange }) => {
setTextValue(e.target.value); setTextValue(e.target.value);
}; };
const parseAndUpdate = (value: string) => { const parseAndUpdate = (value: string) => {
const lines = value.split('\n').filter(line => line.trim() !== ''); const lines = value.split("\n").filter((line) => line.trim() !== "");
const parsedSteps: Step[] = lines.map((line, idx) => { const parsedSteps: Step[] = lines.map((line, idx) => {
return { step_number: idx + 1, instruction: line } return { step_number: idx + 1, instruction: line };
}) });
if (onChange) onChange(parsedSteps); if (onChange) onChange(parsedSteps);
}; };
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => { const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (e.key === 'Enter') { if (e.key === "Enter") {
parseAndUpdate(textValue); parseAndUpdate(textValue);
} }
}; };
const handleBlur = () => { const handleBlur = () => {
parseAndUpdate(textValue); parseAndUpdate(textValue);
document.body.style.overflow = "";
};
const manageOverflow = () => {
const lineCount = textValue
.split("\n")
.filter((line) => line.trim() !== "").length;
if (lineCount > 8) {
document.body.style.overflow = "hidden";
if (textareaRef.current) {
textareaRef.current.className += " overflow-y-auto"; // Set className for scrolling
}
} else {
document.body.style.overflow = "";
if (textareaRef.current) {
textareaRef.current.className = textareaRef.current.className.replace(
"overflow-y-auto",
"",
);
}
}
};
const handleFocus = () => {
manageOverflow();
};
const handleTouchEnd = () => {
manageOverflow();
}; };
return ( return (
<div> <div>
<h3 className="text-xl font-bold text-[var(--color-secondaryTextDark)]">Steps:</h3> <h3 className="text-xl font-bold text-[var(--color-secondaryTextDark)]">
Steps:
</h3>
<textarea <textarea
rows={8} rows={8}
value={textValue} value={textValue}
onChange={handleInputChange} onChange={handleInputChange}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
onBlur={handleBlur} onBlur={handleBlur}
onFocus={handleFocus}
onTouchEnd={handleTouchEnd}
placeholder="Enter steps separated by new line" placeholder="Enter steps separated by new line"
className="mb-4 p-2 border border-[var(--color-primaryBorder)] rounded w-full" className="mb-4 p-2 border border-[var(--color-primaryBorder)] rounded w-full overflow-y-auto"
style={{ overflowY: "auto" }}
/> />
</div> </div>
); );