Exersice name field focus opens keyboard. Typing filters the options.
This commit is contained in:
@@ -4,15 +4,18 @@ interface FilledInputProps {
|
|||||||
label: string;
|
label: string;
|
||||||
value: string | number;
|
value: string | number;
|
||||||
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||||
|
onFocus?: (e: React.FocusEvent<HTMLInputElement>) => void;
|
||||||
|
onBlur?: (e: React.FocusEvent<HTMLInputElement>) => void;
|
||||||
type?: string;
|
type?: string;
|
||||||
icon?: React.ReactNode;
|
icon?: React.ReactNode;
|
||||||
autoFocus?: boolean;
|
autoFocus?: boolean;
|
||||||
step?: string;
|
step?: string;
|
||||||
inputMode?: "search" | "text" | "email" | "tel" | "url" | "none" | "numeric" | "decimal";
|
inputMode?: "search" | "text" | "email" | "tel" | "url" | "none" | "numeric" | "decimal";
|
||||||
autocapitalize?: "off" | "none" | "on" | "sentences" | "words" | "characters";
|
autocapitalize?: "off" | "none" | "on" | "sentences" | "words" | "characters";
|
||||||
|
autoComplete?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const FilledInput: React.FC<FilledInputProps> = ({ label, value, onChange, type = "number", icon, autoFocus, step, inputMode, autocapitalize }) => (
|
const FilledInput: React.FC<FilledInputProps> = ({ label, value, onChange, onFocus, onBlur, type = "number", icon, autoFocus, step, inputMode, autocapitalize, autoComplete }) => (
|
||||||
<div className="relative group bg-surface-container-high rounded-t-lg border-b border-outline-variant hover:bg-white/5 focus-within:border-primary transition-colors">
|
<div className="relative group bg-surface-container-high rounded-t-lg border-b border-outline-variant hover:bg-white/5 focus-within:border-primary transition-colors">
|
||||||
<label className="absolute top-2 left-4 text-[10px] font-medium text-on-surface-variant flex items-center gap-1">
|
<label className="absolute top-2 left-4 text-[10px] font-medium text-on-surface-variant flex items-center gap-1">
|
||||||
{icon} {label}
|
{icon} {label}
|
||||||
@@ -26,7 +29,10 @@ const FilledInput: React.FC<FilledInputProps> = ({ label, value, onChange, type
|
|||||||
placeholder="0"
|
placeholder="0"
|
||||||
value={value}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
onFocus={onFocus}
|
||||||
|
onBlur={onBlur}
|
||||||
autoCapitalize={autocapitalize}
|
autoCapitalize={autocapitalize}
|
||||||
|
autoComplete={autoComplete}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ const Tracker: React.FC<TrackerProps> = ({ userId, userWeight, activeSession, ac
|
|||||||
const [plans, setPlans] = useState<WorkoutPlan[]>([]);
|
const [plans, setPlans] = useState<WorkoutPlan[]>([]);
|
||||||
const [selectedExercise, setSelectedExercise] = useState<ExerciseDef | null>(null);
|
const [selectedExercise, setSelectedExercise] = useState<ExerciseDef | null>(null);
|
||||||
const [lastSet, setLastSet] = useState<WorkoutSet | undefined>(undefined);
|
const [lastSet, setLastSet] = useState<WorkoutSet | undefined>(undefined);
|
||||||
|
const [searchQuery, setSearchQuery] = useState<string>('');
|
||||||
|
const [showSuggestions, setShowSuggestions] = useState(false);
|
||||||
|
|
||||||
// Timer State
|
// Timer State
|
||||||
const [elapsedTime, setElapsedTime] = useState<string>('00:00:00');
|
const [elapsedTime, setElapsedTime] = useState<string>('00:00:00');
|
||||||
@@ -174,11 +176,19 @@ const Tracker: React.FC<TrackerProps> = ({ userId, userWeight, activeSession, ac
|
|||||||
if (selectedExercise.type !== ExerciseType.HIGH_JUMP) {
|
if (selectedExercise.type !== ExerciseType.HIGH_JUMP) {
|
||||||
setHeight('');
|
setHeight('');
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
setSearchQuery(''); // Clear search query if no exercise is selected
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
updateSelection();
|
updateSelection();
|
||||||
}, [selectedExercise, userId]);
|
}, [selectedExercise, userId]);
|
||||||
|
|
||||||
|
const filteredExercises = searchQuery === ''
|
||||||
|
? exercises
|
||||||
|
: exercises.filter(ex =>
|
||||||
|
ex.name.toLowerCase().includes(searchQuery.toLowerCase())
|
||||||
|
);
|
||||||
|
|
||||||
const handleStart = (plan?: WorkoutPlan) => {
|
const handleStart = (plan?: WorkoutPlan) => {
|
||||||
if (plan && plan.description) {
|
if (plan && plan.description) {
|
||||||
setShowPlanPrep(plan);
|
setShowPlanPrep(plan);
|
||||||
@@ -486,25 +496,46 @@ const Tracker: React.FC<TrackerProps> = ({ userId, userWeight, activeSession, ac
|
|||||||
<div className="flex-1 overflow-y-auto p-4 pb-32 space-y-6">
|
<div className="flex-1 overflow-y-auto p-4 pb-32 space-y-6">
|
||||||
|
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<select
|
<FilledInput
|
||||||
className="w-full p-4 pr-12 bg-transparent border border-outline rounded-lg text-on-surface appearance-none focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary text-lg font-normal"
|
label={t('select_exercise', lang)}
|
||||||
value={selectedExercise?.id || ''}
|
value={searchQuery}
|
||||||
onChange={(e) => setSelectedExercise(exercises.find(ex => ex.id === e.target.value) || null)}
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
>
|
setSearchQuery(e.target.value);
|
||||||
<option value="" disabled>{t('select_exercise', lang)}</option>
|
setShowSuggestions(true);
|
||||||
{exercises
|
}}
|
||||||
.map(ex => (
|
onFocus={() => setShowSuggestions(true)}
|
||||||
<option key={ex.id} value={ex.id} className="bg-surface-container text-on-surface">{ex.name}</option>
|
onBlur={() => setTimeout(() => setShowSuggestions(false), 100)} // Delay hiding to allow click
|
||||||
))}
|
icon={<Dumbbell size={10} />}
|
||||||
</select>
|
autoComplete="off"
|
||||||
<ChevronDown className="absolute right-4 top-1/2 -translate-y-1/2 text-on-surface-variant pointer-events-none" size={24} />
|
type="text"
|
||||||
|
/>
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsCreating(true)}
|
onClick={() => setIsCreating(true)}
|
||||||
className="absolute right-12 top-1/2 -translate-y-1/2 p-2 text-primary hover:bg-primary-container/20 rounded-full"
|
className="absolute right-2 top-1/2 -translate-y-1/2 p-2 text-primary hover:bg-primary-container/20 rounded-full z-10"
|
||||||
>
|
>
|
||||||
<Plus size={24} />
|
<Plus size={24} />
|
||||||
</button>
|
</button>
|
||||||
|
{showSuggestions && (
|
||||||
|
<div className="absolute top-full left-0 w-full bg-surface-container rounded-xl shadow-elevation-3 overflow-hidden z-20 mt-1 max-h-60 overflow-y-auto animate-in fade-in slide-in-from-top-2">
|
||||||
|
{filteredExercises.length > 0 ? (
|
||||||
|
filteredExercises.map(ex => (
|
||||||
|
<button
|
||||||
|
key={ex.id}
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedExercise(ex);
|
||||||
|
setSearchQuery(ex.name);
|
||||||
|
setShowSuggestions(false);
|
||||||
|
}}
|
||||||
|
className="w-full text-left px-4 py-3 text-on-surface hover:bg-surface-container-high transition-colors text-lg"
|
||||||
|
>
|
||||||
|
{ex.name}
|
||||||
|
</button>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div className="px-4 py-3 text-on-surface-variant text-lg">{t('no_exercises_found', lang)}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{selectedExercise && (
|
{selectedExercise && (
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user