Add auto complete
This commit is contained in:
parent
9f781d784d
commit
013d5692bf
@ -1863,3 +1863,49 @@ html {
|
|||||||
color: var(--text-muted);
|
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}
|
onSubmit={handleFormSubmit}
|
||||||
editingRecipe={editingRecipe}
|
editingRecipe={editingRecipe}
|
||||||
currentUser={user}
|
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 { 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 [name, setName] = useState("");
|
||||||
const [mealType, setMealType] = useState("lunch");
|
const [mealType, setMealType] = useState("lunch");
|
||||||
const [timeMinutes, setTimeMinutes] = useState(15);
|
const [timeMinutes, setTimeMinutes] = useState(15);
|
||||||
@ -11,6 +12,16 @@ function RecipeFormDrawer({ open, onClose, onSubmit, editingRecipe = null, curre
|
|||||||
const [ingredients, setIngredients] = useState([""]);
|
const [ingredients, setIngredients] = useState([""]);
|
||||||
const [steps, setSteps] = 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 lastIngredientRef = useRef(null);
|
||||||
const lastStepRef = useRef(null);
|
const lastStepRef = useRef(null);
|
||||||
|
|
||||||
@ -162,9 +173,10 @@ function RecipeFormDrawer({ open, onClose, onSubmit, editingRecipe = null, curre
|
|||||||
|
|
||||||
<div className="field">
|
<div className="field">
|
||||||
<label>המתכון של:</label>
|
<label>המתכון של:</label>
|
||||||
<input
|
<AutocompleteInput
|
||||||
value={madeBy}
|
value={madeBy}
|
||||||
onChange={(e) => setMadeBy(e.target.value)}
|
onChange={(e) => setMadeBy(e.target.value)}
|
||||||
|
suggestions={uniqueMadeBy}
|
||||||
placeholder="שם האדם שיצר את הגרסה הזו..."
|
placeholder="שם האדם שיצר את הגרסה הזו..."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -212,8 +224,8 @@ function RecipeFormDrawer({ open, onClose, onSubmit, editingRecipe = null, curre
|
|||||||
<div className="dynamic-list">
|
<div className="dynamic-list">
|
||||||
{ingredients.map((val, idx) => (
|
{ingredients.map((val, idx) => (
|
||||||
<div key={idx} className="dynamic-row">
|
<div key={idx} className="dynamic-row">
|
||||||
<input
|
<AutocompleteInput
|
||||||
ref={idx === ingredients.length - 1 ? lastIngredientRef : null}
|
inputRef={idx === ingredients.length - 1 ? lastIngredientRef : null}
|
||||||
value={val}
|
value={val}
|
||||||
onChange={(e) => handleChangeIngredient(idx, e.target.value)}
|
onChange={(e) => handleChangeIngredient(idx, e.target.value)}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
@ -222,6 +234,7 @@ function RecipeFormDrawer({ open, onClose, onSubmit, editingRecipe = null, curre
|
|||||||
handleAddIngredient();
|
handleAddIngredient();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
suggestions={uniqueIngredients}
|
||||||
placeholder="למשל: 2 ביצים"
|
placeholder="למשל: 2 ביצים"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user