feat: simplify latest carousel and use leaflet import
This commit is contained in:
parent
1257042a66
commit
74f4853686
3 changed files with 53 additions and 128 deletions
|
|
@ -134,21 +134,32 @@ p {
|
|||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.latest-grid {
|
||||
.latest-carousel {
|
||||
display: grid;
|
||||
grid-template-columns: 1.2fr 0.8fr;
|
||||
gap: 14px;
|
||||
margin-top: 14px;
|
||||
gap: 12px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.latest-card {
|
||||
.carousel-window {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
border: 1px solid rgba(148, 163, 184, 0.2);
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
}
|
||||
|
||||
.carousel-track {
|
||||
display: flex;
|
||||
transition: transform 500ms ease;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.carousel-slide {
|
||||
min-width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
min-height: 240px;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.latest-cover {
|
||||
|
|
@ -168,67 +179,6 @@ p {
|
|||
gap: 6px;
|
||||
}
|
||||
|
||||
.latest-rail {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
max-height: 320px;
|
||||
overflow: auto;
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
.rail-item {
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: 76px 1fr;
|
||||
gap: 10px;
|
||||
padding: 8px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(148, 163, 184, 0.16);
|
||||
background: rgba(255, 255, 255, 0.01);
|
||||
cursor: pointer;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.rail-item.active {
|
||||
border-color: var(--accent);
|
||||
background: rgba(34, 211, 238, 0.08);
|
||||
}
|
||||
|
||||
.rail-thumb {
|
||||
width: 100%;
|
||||
height: 64px;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
background: rgba(14, 165, 233, 0.06);
|
||||
}
|
||||
|
||||
.rail-thumb img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.rail-fallback {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(110deg, rgba(34, 211, 238, 0.1), rgba(14, 165, 233, 0.1));
|
||||
}
|
||||
|
||||
.rail-text {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.rail-title {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.rail-sub {
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.dot-row {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useI18n } from '../components/I18nProvider';
|
||||
|
||||
|
|
@ -47,25 +48,10 @@ function haversineKm(a: LatLng, b: LatLng) {
|
|||
return 2 * R * Math.atan2(Math.sqrt(h), Math.sqrt(1 - h));
|
||||
}
|
||||
|
||||
async function loadLeaflet(): Promise<any> {
|
||||
if (typeof window === 'undefined') return Promise.reject();
|
||||
if ((window as any).L) return (window as any).L;
|
||||
const linkId = 'leaflet-css';
|
||||
if (!document.getElementById(linkId)) {
|
||||
const link = document.createElement('link');
|
||||
link.id = linkId;
|
||||
link.rel = 'stylesheet';
|
||||
link.href = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css';
|
||||
document.head.appendChild(link);
|
||||
}
|
||||
try {
|
||||
const mod = await import('leaflet');
|
||||
(window as any).L = mod;
|
||||
return mod;
|
||||
} catch (err) {
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
const LeafletWrapper = dynamic(async () => {
|
||||
const L = await import('leaflet');
|
||||
return L;
|
||||
}, { ssr: false });
|
||||
|
||||
function ListingsMap({
|
||||
listings,
|
||||
|
|
@ -88,7 +74,7 @@ function ListingsMap({
|
|||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
loadLeaflet()
|
||||
LeafletWrapper
|
||||
.then((L) => {
|
||||
if (cancelled) return;
|
||||
setReady(true);
|
||||
|
|
|
|||
67
app/page.tsx
67
app/page.tsx
|
|
@ -127,48 +127,37 @@ export default function HomePage() {
|
|||
) : !activeListing ? (
|
||||
<p style={{ color: '#cbd5e1', marginTop: 10 }}>{t('mapNoResults')}</p>
|
||||
) : (
|
||||
<div className="latest-grid" onMouseEnter={() => setPaused(true)} onMouseLeave={() => setPaused(false)}>
|
||||
<div className="latest-card">
|
||||
{activeListing.coverImage ? (
|
||||
<img src={activeListing.coverImage} alt={activeListing.title} className="latest-cover" />
|
||||
) : (
|
||||
<div className="latest-cover placeholder" />
|
||||
)}
|
||||
<div className="latest-meta">
|
||||
<span className="badge">
|
||||
{activeListing.city}, {activeListing.region}
|
||||
</span>
|
||||
<h3 style={{ margin: '6px 0 4px' }}>{activeListing.title}</h3>
|
||||
<p style={{ margin: 0 }}>{activeListing.teaser}</p>
|
||||
<div style={{ display: 'flex', gap: 8, marginTop: 10, flexWrap: 'wrap' }}>
|
||||
<Link className="button secondary" href={`/listings/${activeListing.slug}`}>
|
||||
{t('openListing')}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="latest-rail">
|
||||
{latest.map((item, idx) => (
|
||||
<button
|
||||
key={item.id}
|
||||
className={`rail-item ${idx === activeIndex ? 'active' : ''}`}
|
||||
onClick={() => setActiveIndex(idx)}
|
||||
>
|
||||
<div className="rail-thumb">
|
||||
{item.coverImage ? <img src={item.coverImage} alt={item.title} /> : <div className="rail-fallback" />}
|
||||
</div>
|
||||
<div className="rail-text">
|
||||
<div className="rail-title">{item.title}</div>
|
||||
<div className="rail-sub">{item.city}, {item.region}</div>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
<div className="dot-row">
|
||||
{latest.map((_, idx) => (
|
||||
<span key={idx} className={`dot ${idx === activeIndex ? 'active' : ''}`} onClick={() => setActiveIndex(idx)} />
|
||||
<div className="latest-carousel" onMouseEnter={() => setPaused(true)} onMouseLeave={() => setPaused(false)}>
|
||||
<div className="carousel-window">
|
||||
<div className="carousel-track" style={{ transform: `translateX(-${activeIndex * 100}%)` }}>
|
||||
{latest.map((item) => (
|
||||
<article key={item.id} className="carousel-slide">
|
||||
{item.coverImage ? (
|
||||
<img src={item.coverImage} alt={item.title} className="latest-cover" />
|
||||
) : (
|
||||
<div className="latest-cover placeholder" />
|
||||
)}
|
||||
<div className="latest-meta">
|
||||
<span className="badge">
|
||||
{item.city}, {item.region}
|
||||
</span>
|
||||
<h3 style={{ margin: '6px 0 4px' }}>{item.title}</h3>
|
||||
<p style={{ margin: 0 }}>{item.teaser}</p>
|
||||
<div style={{ display: 'flex', gap: 8, marginTop: 10, flexWrap: 'wrap' }}>
|
||||
<Link className="button secondary" href={`/listings/${item.slug}`}>
|
||||
{t('openListing')}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="dot-row">
|
||||
{latest.map((_, idx) => (
|
||||
<span key={idx} className={`dot ${idx === activeIndex ? 'active' : ''}`} onClick={() => setActiveIndex(idx)} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue