Redesign station cards with compact layout, improved typography, and expandable details
- Reduce header size and weight, convert all-caps brand names to title case - Replace address line with distance-only in collapsed state, move brand label to expanded section - Apply monospace font to pricing, reduce size and weight across labels - Move badge list and full details into expandable section - Normalize font weights throughout (semibold for headings, medium for labels) - Create `.pill` component class with `.is-active` state for consistent filter styling - Apply pill styling to SearchBar filters, StationList sort buttons, and brand filter - Add `name` attributes to fuel type and radius selects - Update package dependencies (@tailwindcss/node, @tailwindcss/oxide, @rolldown/*)
This commit is contained in:
@@ -1,19 +1,12 @@
|
||||
<template>
|
||||
<div class="flex flex-wrap items-center gap-2 md:gap-2.5 py-3 border-b border-zinc-200">
|
||||
<div class="flex flex-wrap items-center gap-2 md:gap-2.5">
|
||||
<!-- Leading label -->
|
||||
<span class="hidden md:inline text-xs font-mono uppercase tracking-widest text-zinc-400 mr-1">
|
||||
Refine
|
||||
</span>
|
||||
|
||||
<!-- Fuel type -->
|
||||
<label
|
||||
:class="[
|
||||
'relative group inline-flex items-center gap-2 h-10 pl-3 pr-2 rounded-full border transition-colors cursor-pointer',
|
||||
fuelType !== DEFAULTS.fuelType
|
||||
? 'bg-primary/10 border-primary text-primary'
|
||||
: 'bg-white border-zinc-200 hover:border-zinc-300',
|
||||
]"
|
||||
>
|
||||
<label :class="{ 'is-active': fuelType !== DEFAULTS.fuelType }" class="pill group">
|
||||
<iconify-icon class="text-sm opacity-70" icon="lucide:fuel"></iconify-icon>
|
||||
<span class="text-sm font-medium">{{ fuelLabel }}</span>
|
||||
<iconify-icon class="text-sm opacity-50 group-hover:opacity-100" icon="lucide:chevron-down"></iconify-icon>
|
||||
@@ -21,20 +14,14 @@
|
||||
v-model="fuelType"
|
||||
aria-label="Fuel type"
|
||||
class="absolute inset-0 opacity-0 cursor-pointer"
|
||||
name="fuelType"
|
||||
>
|
||||
<option v-for="fuel in FUEL_TYPES" :key="fuel.value" :value="fuel.value">{{ fuel.label }}</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<!-- Radius -->
|
||||
<label
|
||||
:class="[
|
||||
'relative group inline-flex items-center gap-2 h-10 pl-3 pr-2 rounded-full border transition-colors cursor-pointer',
|
||||
radius !== DEFAULTS.radius
|
||||
? 'bg-primary/10 border-primary text-primary'
|
||||
: 'bg-white border-zinc-200 hover:border-zinc-300',
|
||||
]"
|
||||
>
|
||||
<label :class="{ 'is-active': radius !== DEFAULTS.radius }" class="pill group">
|
||||
<iconify-icon class="text-sm opacity-70" icon="lucide:circle-dot"></iconify-icon>
|
||||
<span class="text-sm font-medium">{{ radius }} miles</span>
|
||||
<iconify-icon class="text-sm opacity-50 group-hover:opacity-100" icon="lucide:chevron-down"></iconify-icon>
|
||||
@@ -42,6 +29,7 @@
|
||||
v-model.number="radius"
|
||||
aria-label="Search radius"
|
||||
class="absolute inset-0 opacity-0 cursor-pointer"
|
||||
name="radius"
|
||||
>
|
||||
<option :value="5">5 miles</option>
|
||||
<option :value="10">10 miles</option>
|
||||
@@ -51,13 +39,9 @@
|
||||
|
||||
<!-- Show / hide map -->
|
||||
<button
|
||||
:class="{ 'is-active': mapOpen }"
|
||||
class="pill"
|
||||
:aria-expanded="mapOpen"
|
||||
:class="[
|
||||
'inline-flex items-center gap-2 h-10 pl-3 pr-2 rounded-full border transition-colors cursor-pointer',
|
||||
mapOpen
|
||||
? 'bg-primary/10 border-primary text-primary'
|
||||
: 'bg-white border-zinc-200 text-zinc-700 hover:border-zinc-300',
|
||||
]"
|
||||
aria-controls="leaflet-map-panel"
|
||||
type="button"
|
||||
@click="emit('toggle-map')"
|
||||
|
||||
@@ -13,18 +13,15 @@
|
||||
>
|
||||
<div class="flex justify-between items-start gap-3">
|
||||
<div class="space-y-0.5 min-w-0 flex-1">
|
||||
<p v-if="brandLabel" class="text-[10px] font-black uppercase tracking-widest text-zinc-500">
|
||||
{{ brandLabel }}
|
||||
</p>
|
||||
<h4 class="font-bold text-lg text-zinc-800 truncate">{{ station.name }}</h4>
|
||||
<h4 class="font-semibold text-sm text-zinc-800 truncate">{{ displayName }}</h4>
|
||||
<template v-if="!expanded">
|
||||
<p class="text-xs text-zinc-500 flex items-center gap-1">
|
||||
<iconify-icon class="text-xs" icon="lucide:map-pin"></iconify-icon>
|
||||
<span class="truncate">{{ locationLine }}</span>
|
||||
<span>{{ distanceMiles }} mi</span>
|
||||
</p>
|
||||
<p v-if="updatedAgo" :class="[priceColor, 'text-xs flex items-center gap-1 font-semibold']">
|
||||
<p v-if="updatedAgo" :class="[priceColor, 'text-xs flex items-center gap-1']">
|
||||
<iconify-icon class="text-xs" icon="lucide:clock"></iconify-icon>
|
||||
<span>Updated {{ updatedAgo }}</span>
|
||||
<span>{{ updatedAgo }}</span>
|
||||
</p>
|
||||
</template>
|
||||
</div>
|
||||
@@ -38,14 +35,14 @@
|
||||
>
|
||||
<iconify-icon class="text-lg" icon="lucide:navigation"></iconify-icon>
|
||||
</a>
|
||||
<div class="text-right shrink-0">
|
||||
<div :class="priceColor" class="text-xl font-black">
|
||||
{{ station.price }}<span class="text-sm font-bold uppercase ml-0.5">p</span>
|
||||
<div class="text-right shrink-0 font-mono font-medium">
|
||||
<div :class="priceColor" class="text-zinc-900 tabular-nums">
|
||||
{{ station.price }}p
|
||||
</div>
|
||||
<p :class="priceColor" class="text-[10px] font-bold uppercase tracking-wider">
|
||||
<p :class="priceColor" class="text-[10px]">
|
||||
{{ statusLabel }}
|
||||
</p>
|
||||
<p v-if="priceDelta" :class="priceDeltaColor" class="text-[10px] font-bold mt-0.5">
|
||||
<p v-if="priceDelta" :class="priceDeltaColor" class="text-[11px] font-semibold mt-0.5">
|
||||
{{ priceDelta }}
|
||||
</p>
|
||||
</div>
|
||||
@@ -60,6 +57,10 @@
|
||||
leave-to-class="opacity-0"
|
||||
>
|
||||
<div v-if="expanded" class="border-t border-zinc-200 pt-3 space-y-3">
|
||||
<p v-if="brandLabel" class="text-[10px] font-black uppercase tracking-widest text-zinc-500">
|
||||
{{ brandLabel }}
|
||||
</p>
|
||||
|
||||
<div v-if="badges.length" class="flex flex-wrap gap-1.5">
|
||||
<span
|
||||
v-for="badge in badges"
|
||||
@@ -207,9 +208,10 @@ const statusLabel = computed(() => reliabilityInfo.value.label)
|
||||
|
||||
const distanceMiles = computed(() => (props.station.distance_km * 0.621371).toFixed(1))
|
||||
|
||||
const locationLine = computed(() => {
|
||||
const parts = [props.station.address, `${distanceMiles.value} mi`].filter(Boolean)
|
||||
return parts.join(' • ')
|
||||
const displayName = computed(() => {
|
||||
const name = props.station.name ?? ''
|
||||
if (name !== name.toUpperCase()) return name
|
||||
return name.toLowerCase().replace(/\b\w/g, c => c.toUpperCase())
|
||||
})
|
||||
|
||||
const fullAddress = computed(() => {
|
||||
|
||||
@@ -8,14 +8,10 @@
|
||||
<button
|
||||
v-for="option in sortOptions"
|
||||
:key="option.value"
|
||||
:class="[
|
||||
'inline-flex items-center gap-2 h-10 px-3 rounded-full border transition-colors cursor-pointer',
|
||||
currentSort === option.value
|
||||
? 'bg-primary/10 border-primary text-primary'
|
||||
: 'bg-white border-zinc-200 text-zinc-700 hover:border-zinc-300',
|
||||
]"
|
||||
@click="emit('sort', option.value)"
|
||||
:class="{ 'is-active': currentSort === option.value }"
|
||||
class="pill"
|
||||
type="button"
|
||||
@click="emit('sort', option.value)"
|
||||
>
|
||||
<iconify-icon :icon="option.icon" class="text-sm opacity-70"></iconify-icon>
|
||||
<span class="text-sm font-medium">{{ option.label }}</span>
|
||||
@@ -24,12 +20,8 @@
|
||||
<!-- Brand filter -->
|
||||
<label
|
||||
v-if="brands.length > 1"
|
||||
:class="[
|
||||
'relative group inline-flex items-center gap-2 h-10 pl-3 pr-2 rounded-full border transition-colors cursor-pointer',
|
||||
brandFilter
|
||||
? 'bg-primary/10 border-primary text-primary'
|
||||
: 'bg-white border-zinc-200 text-zinc-700 hover:border-zinc-300',
|
||||
]"
|
||||
:class="{ 'is-active': brandFilter }"
|
||||
class="pill group"
|
||||
>
|
||||
<iconify-icon class="text-sm opacity-70" icon="lucide:tag"></iconify-icon>
|
||||
<span class="text-sm font-medium">{{ brandFilter || 'All brands' }}</span>
|
||||
|
||||
Reference in New Issue
Block a user