Add auto complete
This commit is contained in:
parent
9f781d784d
commit
013d5692bf
@ -1863,3 +1863,49 @@ html {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* Autocomplete Input Styles */
|
||||
.autocomplete-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.autocomplete-wrapper input {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.autocomplete-suggestion {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
pointer-events: none;
|
||||
border-radius: 10px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.55rem 0.75rem;
|
||||
font-size: 0.9rem;
|
||||
min-height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--text-muted);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.autocomplete-suggestion {
|
||||
padding: 0.4rem 0.65rem;
|
||||
min-height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.autocomplete-typed {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.autocomplete-rest {
|
||||
color: var(--text-muted);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
|
||||
@ -580,6 +580,7 @@ function App() {
|
||||
onSubmit={handleFormSubmit}
|
||||
editingRecipe={editingRecipe}
|
||||
currentUser={user}
|
||||
allRecipes={recipes}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
49
frontend/src/components/AutocompleteInput.jsx
Normal file
49
frontend/src/components/AutocompleteInput.jsx
Normal file
@ -0,0 +1,49 @@
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
|
||||
function AutocompleteInput({ value, onChange, onKeyDown, suggestions = [], placeholder, inputRef, ...props }) {
|
||||
const [suggestion, setSuggestion] = useState("");
|
||||
const localRef = useRef(null);
|
||||
const ref = inputRef || localRef;
|
||||
|
||||
useEffect(() => {
|
||||
if (value && suggestions.length > 0) {
|
||||
const match = suggestions.find(s =>
|
||||
s.toLowerCase().startsWith(value.toLowerCase()) && s.toLowerCase() !== value.toLowerCase()
|
||||
);
|
||||
setSuggestion(match || "");
|
||||
} else {
|
||||
setSuggestion("");
|
||||
}
|
||||
}, [value, suggestions]);
|
||||
|
||||
const handleKeyDown = (e) => {
|
||||
if (e.key === 'Tab' && suggestion) {
|
||||
e.preventDefault();
|
||||
onChange({ target: { value: suggestion } });
|
||||
setSuggestion("");
|
||||
} else if (onKeyDown) {
|
||||
onKeyDown(e);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="autocomplete-wrapper">
|
||||
<input
|
||||
ref={ref}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder={placeholder}
|
||||
{...props}
|
||||
/>
|
||||
{suggestion && (
|
||||
<div className="autocomplete-suggestion">
|
||||
<span className="autocomplete-typed">{value}</span>
|
||||
<span className="autocomplete-rest">{suggestion.slice(value.length)}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default AutocompleteInput;
|
||||
@ -1,6 +1,7 @@
|
||||
import { useEffect, useState, useRef } from "react";
|
||||
import AutocompleteInput from "./AutocompleteInput";
|
||||
|
||||
function RecipeFormDrawer({ open, onClose, onSubmit, editingRecipe = null, currentUser = null }) {
|
||||
function RecipeFormDrawer({ open, onClose, onSubmit, editingRecipe = null, currentUser = null, allRecipes = [] }) {
|
||||
const [name, setName] = useState("");
|
||||
const [mealType, setMealType] = useState("lunch");
|
||||
const [timeMinutes, setTimeMinutes] = useState(15);
|
||||
@ -10,6 +11,16 @@ function RecipeFormDrawer({ open, onClose, onSubmit, editingRecipe = null, curre
|
||||
|
||||
const [ingredients, setIngredients] = useState([""]);
|
||||
const [steps, setSteps] = useState([""]);
|
||||
|
||||
// Extract unique made_by values for autocomplete
|
||||
const uniqueMadeBy = Array.from(
|
||||
new Set(allRecipes.map(r => r.made_by).filter(Boolean))
|
||||
).sort();
|
||||
|
||||
// Extract unique ingredients for autocomplete
|
||||
const uniqueIngredients = Array.from(
|
||||
new Set(allRecipes.flatMap(r => r.ingredients || []).filter(Boolean))
|
||||
).sort();
|
||||
|
||||
const lastIngredientRef = useRef(null);
|
||||
const lastStepRef = useRef(null);
|
||||
@ -162,9 +173,10 @@ function RecipeFormDrawer({ open, onClose, onSubmit, editingRecipe = null, curre
|
||||
|
||||
<div className="field">
|
||||
<label>המתכון של:</label>
|
||||
<input
|
||||
<AutocompleteInput
|
||||
value={madeBy}
|
||||
onChange={(e) => setMadeBy(e.target.value)}
|
||||
suggestions={uniqueMadeBy}
|
||||
placeholder="שם האדם שיצר את הגרסה הזו..."
|
||||
/>
|
||||
</div>
|
||||
@ -212,8 +224,8 @@ function RecipeFormDrawer({ open, onClose, onSubmit, editingRecipe = null, curre
|
||||
<div className="dynamic-list">
|
||||
{ingredients.map((val, idx) => (
|
||||
<div key={idx} className="dynamic-row">
|
||||
<input
|
||||
ref={idx === ingredients.length - 1 ? lastIngredientRef : null}
|
||||
<AutocompleteInput
|
||||
inputRef={idx === ingredients.length - 1 ? lastIngredientRef : null}
|
||||
value={val}
|
||||
onChange={(e) => handleChangeIngredient(idx, e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
@ -222,6 +234,7 @@ function RecipeFormDrawer({ open, onClose, onSubmit, editingRecipe = null, curre
|
||||
handleAddIngredient();
|
||||
}
|
||||
}}
|
||||
suggestions={uniqueIngredients}
|
||||
placeholder="למשל: 2 ביצים"
|
||||
/>
|
||||
<button
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user