Redesign search UI with unified input, expandable filters, and integrated map controls
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (8.3) (push) Has been cancelled
tests / ci (8.4) (push) Has been cancelled
tests / ci (8.5) (push) Has been cancelled

- Consolidate HeroSearch into single responsive form with inline geolocation button and submit actions
- Transform SearchBar into pill-based filter bar with visual state indicators (active filters highlighted)
- Move map toggle from separate component into SearchBar with open/close state management
- Redesign StationList sort controls as pills with icons, move brand filter inline, add result count
- Expand LeafletMap to full-width panel (96 viewport height) controlled by parent open state
- Remove nested mobile/desktop layouts in HeroSearch in favor of single adaptive form
- Add "Refine" and "Sort" labels to filter groups, implement clear-all filters button
- Show verdict card only before first search on mobile, hide after results load
- Position StatsRow within hero gradient, move results section into same gradient container
- Update map initialization to only occur when panel is open, destroy on close
- Add accessibility labels (aria-expanded, aria-controls) to map toggle button
This commit is contained in:
Ovidiu U
2026-04-22 09:38:23 +01:00
parent afe459f248
commit dd9bd95657
6 changed files with 352 additions and 291 deletions

View File

@@ -1,74 +1,50 @@
<template>
<div class="w-full max-w-xl">
<!-- Mobile layout: stacked input + full-width geolocation CTA -->
<div class="md:hidden space-y-3">
<label class="relative block">
<span class="sr-only">Postcode</span>
<iconify-icon
class="absolute left-4 top-1/2 -translate-y-1/2 text-zinc-500"
icon="lucide:map-pin"
style="font-size:18px;"
></iconify-icon>
<input
v-model="postcode"
class="w-full h-[52px] pl-11 pr-4 bg-white border border-zinc-300 rounded-xl text-base text-zinc-800 placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-accent/40 focus:border-accent"
placeholder="Enter a UK postcode"
type="text"
@keyup.enter="submitPostcode"
/>
</label>
<form class="w-full max-w-xl" @submit.prevent="submitPostcode">
<label class="flex items-center gap-2 h-14 md:h-15 pl-3.5 md:pl-4 pr-1.5 md:pr-2 bg-white md:bg-surface border border-zinc-200 rounded-2xl focus-within:border-primary transition-colors md:shadow-[0_20px_40px_-20px_rgba(0,0,0,0.12)]">
<iconify-icon class="text-zinc-400 text-lg shrink-0" icon="lucide:map-pin"></iconify-icon>
<input
v-model="postcode"
autocomplete="postal-code"
class="flex-1 min-w-0 bg-transparent outline-none text-[15px] md:text-base placeholder:text-zinc-400"
placeholder="Postcode"
type="text"
/>
<!-- Geolocation icon-button visible on mobile AND desktop -->
<button
:disabled="locating"
class="w-full h-14 bg-accent text-white rounded-xl font-semibold text-base flex items-center justify-center gap-2 shadow-lg hover:bg-primary-dark transition-all disabled:opacity-70"
aria-label="Use my location"
class="w-11 h-11 rounded-[10px] bg-zinc-100 text-primary inline-flex items-center justify-center shrink-0 hover:bg-zinc-200 transition-colors disabled:opacity-70"
type="button"
@click="useMyLocation"
>
<iconify-icon :class="{ 'animate-spin': locating }" :icon="locating ? 'lucide:loader-circle' : 'lucide:locate-fixed'" style="font-size:20px;"></iconify-icon>
{{ locating ? 'Getting location…' : 'Use my location' }}
<iconify-icon :class="{ 'animate-spin': locating }" :icon="locating ? 'lucide:loader-circle' : 'lucide:locate-fixed'" class="text-lg"></iconify-icon>
</button>
<p class="font-mono text-[11px] text-zinc-500 text-center">Free · no signup to try</p>
</div>
<!-- Desktop layout: inline postcode pill + geo link below -->
<div class="hidden md:block">
<div class="flex items-stretch bg-white border border-zinc-300 rounded-full h-[60px] pl-5 pr-1.5 shadow-sm focus-within:ring-2 focus-within:ring-accent/40 focus-within:border-accent">
<iconify-icon
class="self-center text-zinc-500 mr-3"
icon="lucide:map-pin"
style="font-size:18px;"
></iconify-icon>
<input
v-model="postcode"
class="flex-1 bg-transparent text-base text-zinc-800 placeholder:text-zinc-500 focus:outline-none"
placeholder="Enter a UK postcode"
type="text"
@keyup.enter="submitPostcode"
/>
<button
:disabled="!postcode.trim()"
class="my-1.5 px-6 bg-accent text-white rounded-full font-semibold text-sm hover:bg-primary-dark transition-all disabled:opacity-40 disabled:cursor-not-allowed"
type="button"
@click="submitPostcode"
>
Check prices
</button>
</div>
<!-- Desktop-only inline submit -->
<button
class="hidden md:inline-flex h-12 px-5 ml-1 rounded-xl bg-primary text-white font-medium text-[15px] items-center gap-2 hover:opacity-90 transition"
type="submit"
>
Check prices
<iconify-icon icon="lucide:arrow-right"></iconify-icon>
</button>
</label>
<div class="flex items-center gap-3 mt-3 text-[13px]">
<button
:disabled="locating"
class="inline-flex items-center gap-1.5 font-medium text-zinc-700 hover:text-accent transition-colors disabled:opacity-70"
type="button"
@click="useMyLocation"
>
<iconify-icon :class="{ 'animate-spin': locating }" :icon="locating ? 'lucide:loader-circle' : 'lucide:locate-fixed'" style="font-size:14px;"></iconify-icon>
{{ locating ? 'Getting location…' : 'Use my location' }}
</button>
<span aria-hidden="true" class="text-zinc-400">·</span>
<span class="font-mono text-zinc-500">Try SW1A 1AA · M1 1AD · EH1 1YZ</span>
</div>
<!-- Mobile-only full-width submit -->
<button
class="md:hidden w-full mt-2.5 h-14 rounded-2xl bg-primary text-white font-medium text-base inline-flex items-center justify-center gap-2 shadow-lg hover:opacity-90 transition"
type="submit"
>
Check prices
<iconify-icon class="text-lg" icon="lucide:arrow-right"></iconify-icon>
</button>
<div class="mt-3 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-zinc-400 justify-center md:justify-start">
<span class="hidden md:inline text-zinc-300">·</span>
<span class="hidden md:inline font-mono">Try SW1A 1AA · M1 1AD · EH1 1YZ</span>
</div>
</div>
</form>
</template>
<script setup>