Improve sport selector UI with visual button components
Replace dropdown select with interactive pill buttons for sport selection - Added SportSelector component with icon-based visual buttons (desktop) - Added MobileSportSelector component for mobile-optimized layout - Added getSportIcon() function to map sport names to appropriate icons - Improved visual feedback with emerald highlight on selection - Better hover states and transitions for improved UX - Works on both desktop and mobile views Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -47,6 +47,7 @@ import {
|
||||
Trophy,
|
||||
Users,
|
||||
Wrench,
|
||||
Zap,
|
||||
} from 'lucide-react';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
@@ -250,6 +251,16 @@ function isSlotSelected(
|
||||
return selectedSlot?.courtId === courtId && selectedSlot.startTime === slot.startTime;
|
||||
}
|
||||
|
||||
function getSportIcon(sportName: string) {
|
||||
const lower = sportName.toLowerCase();
|
||||
if (lower.includes('tenis')) return Zap;
|
||||
if (lower.includes('futbol') || lower.includes('fútbol')) return Dumbbell;
|
||||
if (lower.includes('paddle') || lower.includes('pádel')) return Trophy;
|
||||
if (lower.includes('basquet') || lower.includes('básquet')) return Trophy;
|
||||
if (lower.includes('volei') || lower.includes('vóley')) return Trophy;
|
||||
return Trophy;
|
||||
}
|
||||
|
||||
function PublicBookingThemeToggle() {
|
||||
const { resolvedTheme, toggleTheme } = useTheme();
|
||||
const Icon = resolvedTheme === 'dark' ? Sun : Moon;
|
||||
@@ -553,11 +564,9 @@ function PublicBookingDesktop(props: BookingShellProps) {
|
||||
<h2 className="text-lg font-semibold">Seleccioná tu cancha</h2>
|
||||
<div className="mt-5 space-y-4">
|
||||
{sportSelectionRequired && (
|
||||
<DarkSelect
|
||||
<SportSelector
|
||||
label="Deporte"
|
||||
icon={<Trophy className="size-5" />}
|
||||
value={selectedSportId ?? ''}
|
||||
placeholder="Seleccioná"
|
||||
onValueChange={onSelectSport}
|
||||
options={sports.map((sport) => ({ value: sport.id, label: sport.name }))}
|
||||
/>
|
||||
@@ -693,12 +702,11 @@ function PublicBookingMobile(props: BookingShellProps) {
|
||||
<div className="mt-5 space-y-3">
|
||||
<Panel className="p-3">
|
||||
<h2 className="text-base font-semibold">Seleccioná tu cancha</h2>
|
||||
<div className="mt-3 overflow-hidden rounded-md border border-white/10">
|
||||
<div className="mt-3 space-y-0">
|
||||
{props.sportSelectionRequired && (
|
||||
<MobileSelect
|
||||
<MobileSportSelector
|
||||
label="Deporte"
|
||||
value={props.selectedSportId ?? ''}
|
||||
placeholder="Seleccioná"
|
||||
onValueChange={props.onSelectSport}
|
||||
options={props.sports.map((sport) => ({ value: sport.id, label: sport.name }))}
|
||||
/>
|
||||
@@ -790,41 +798,84 @@ function StatusPanel({ children }: { children?: React.ReactNode }) {
|
||||
);
|
||||
}
|
||||
|
||||
function DarkSelect({
|
||||
function SportSelector({
|
||||
label,
|
||||
icon,
|
||||
value,
|
||||
placeholder,
|
||||
options,
|
||||
onValueChange,
|
||||
}: {
|
||||
label: string;
|
||||
icon: React.ReactNode;
|
||||
value: string;
|
||||
placeholder: string;
|
||||
options: { value: string; label: string }[];
|
||||
onValueChange: (value: string) => void;
|
||||
}) {
|
||||
return (
|
||||
<Field>
|
||||
<FieldLabel className="text-sm text-white/76">{label}</FieldLabel>
|
||||
<Select value={value} onValueChange={onValueChange}>
|
||||
<SelectTrigger className="h-10 rounded-md border-white/10 bg-white/[0.035] text-white">
|
||||
<span className="mr-2 text-white">{icon}</span>
|
||||
<SelectValue placeholder={placeholder} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{options.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{options.map((option) => {
|
||||
const Icon = getSportIcon(option.label);
|
||||
const isSelected = value === option.value;
|
||||
return (
|
||||
<button
|
||||
key={option.value}
|
||||
type="button"
|
||||
onClick={() => onValueChange(option.value)}
|
||||
className={`inline-flex items-center gap-2 rounded-full px-4 py-2 transition-all ${
|
||||
isSelected
|
||||
? 'border border-emerald-500 bg-emerald-500/18 shadow-[inset_0_0_20px_rgba(16,185,129,0.14)] text-white'
|
||||
: 'border border-white/15 bg-white/[0.05] text-white/76 hover:border-white/30 hover:bg-white/[0.08] hover:text-white'
|
||||
}`}
|
||||
>
|
||||
<Icon className="size-4" />
|
||||
<span className="text-sm font-medium">{option.label}</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Field>
|
||||
);
|
||||
}
|
||||
|
||||
function MobileSportSelector({
|
||||
label,
|
||||
value,
|
||||
options,
|
||||
onValueChange,
|
||||
}: {
|
||||
label: string;
|
||||
value: string;
|
||||
options: { value: string; label: string }[];
|
||||
onValueChange: (value: string) => void;
|
||||
}) {
|
||||
return (
|
||||
<div className="border-b border-white/10 bg-white/[0.025] px-3 py-3 last:border-b-0">
|
||||
<span className="mb-2 block text-xs text-white/66">{label}</span>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{options.map((option) => {
|
||||
const Icon = getSportIcon(option.label);
|
||||
const isSelected = value === option.value;
|
||||
return (
|
||||
<button
|
||||
key={option.value}
|
||||
type="button"
|
||||
onClick={() => onValueChange(option.value)}
|
||||
className={`inline-flex items-center gap-1.5 rounded-full px-3 py-1.5 text-xs font-medium transition-all ${
|
||||
isSelected
|
||||
? 'border border-emerald-500 bg-emerald-500/18 shadow-[inset_0_0_15px_rgba(16,185,129,0.1)] text-white'
|
||||
: 'border border-white/15 bg-white/[0.05] text-white/66 hover:border-white/25 hover:bg-white/[0.08] hover:text-white/80'
|
||||
}`}
|
||||
>
|
||||
<Icon className="size-3.5" />
|
||||
<span>{option.label}</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function MobileSelect({
|
||||
label,
|
||||
value,
|
||||
|
||||
Reference in New Issue
Block a user