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;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.latest-grid {
|
.latest-carousel {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1.2fr 0.8fr;
|
gap: 12px;
|
||||||
gap: 14px;
|
margin-top: 12px;
|
||||||
margin-top: 14px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.latest-card {
|
.carousel-window {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
border: 1px solid rgba(148, 163, 184, 0.2);
|
border: 1px solid rgba(148, 163, 184, 0.2);
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
overflow: hidden;
|
|
||||||
background: rgba(255, 255, 255, 0.02);
|
background: rgba(255, 255, 255, 0.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-track {
|
||||||
|
display: flex;
|
||||||
|
transition: transform 500ms ease;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-slide {
|
||||||
|
min-width: 100%;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||||
min-height: 240px;
|
align-items: stretch;
|
||||||
}
|
}
|
||||||
|
|
||||||
.latest-cover {
|
.latest-cover {
|
||||||
|
|
@ -168,67 +179,6 @@ p {
|
||||||
gap: 6px;
|
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 {
|
.dot-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { useI18n } from '../components/I18nProvider';
|
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));
|
return 2 * R * Math.atan2(Math.sqrt(h), Math.sqrt(1 - h));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadLeaflet(): Promise<any> {
|
const LeafletWrapper = dynamic(async () => {
|
||||||
if (typeof window === 'undefined') return Promise.reject();
|
const L = await import('leaflet');
|
||||||
if ((window as any).L) return (window as any).L;
|
return L;
|
||||||
const linkId = 'leaflet-css';
|
}, { ssr: false });
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function ListingsMap({
|
function ListingsMap({
|
||||||
listings,
|
listings,
|
||||||
|
|
@ -88,7 +74,7 @@ function ListingsMap({
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
loadLeaflet()
|
LeafletWrapper
|
||||||
.then((L) => {
|
.then((L) => {
|
||||||
if (cancelled) return;
|
if (cancelled) return;
|
||||||
setReady(true);
|
setReady(true);
|
||||||
|
|
|
||||||
67
app/page.tsx
67
app/page.tsx
|
|
@ -127,48 +127,37 @@ export default function HomePage() {
|
||||||
) : !activeListing ? (
|
) : !activeListing ? (
|
||||||
<p style={{ color: '#cbd5e1', marginTop: 10 }}>{t('mapNoResults')}</p>
|
<p style={{ color: '#cbd5e1', marginTop: 10 }}>{t('mapNoResults')}</p>
|
||||||
) : (
|
) : (
|
||||||
<div className="latest-grid" onMouseEnter={() => setPaused(true)} onMouseLeave={() => setPaused(false)}>
|
<div className="latest-carousel" onMouseEnter={() => setPaused(true)} onMouseLeave={() => setPaused(false)}>
|
||||||
<div className="latest-card">
|
<div className="carousel-window">
|
||||||
{activeListing.coverImage ? (
|
<div className="carousel-track" style={{ transform: `translateX(-${activeIndex * 100}%)` }}>
|
||||||
<img src={activeListing.coverImage} alt={activeListing.title} className="latest-cover" />
|
{latest.map((item) => (
|
||||||
) : (
|
<article key={item.id} className="carousel-slide">
|
||||||
<div className="latest-cover placeholder" />
|
{item.coverImage ? (
|
||||||
)}
|
<img src={item.coverImage} alt={item.title} className="latest-cover" />
|
||||||
<div className="latest-meta">
|
) : (
|
||||||
<span className="badge">
|
<div className="latest-cover placeholder" />
|
||||||
{activeListing.city}, {activeListing.region}
|
)}
|
||||||
</span>
|
<div className="latest-meta">
|
||||||
<h3 style={{ margin: '6px 0 4px' }}>{activeListing.title}</h3>
|
<span className="badge">
|
||||||
<p style={{ margin: 0 }}>{activeListing.teaser}</p>
|
{item.city}, {item.region}
|
||||||
<div style={{ display: 'flex', gap: 8, marginTop: 10, flexWrap: 'wrap' }}>
|
</span>
|
||||||
<Link className="button secondary" href={`/listings/${activeListing.slug}`}>
|
<h3 style={{ margin: '6px 0 4px' }}>{item.title}</h3>
|
||||||
{t('openListing')}
|
<p style={{ margin: 0 }}>{item.teaser}</p>
|
||||||
</Link>
|
<div style={{ display: 'flex', gap: 8, marginTop: 10, flexWrap: 'wrap' }}>
|
||||||
</div>
|
<Link className="button secondary" href={`/listings/${item.slug}`}>
|
||||||
</div>
|
{t('openListing')}
|
||||||
</div>
|
</Link>
|
||||||
<div className="latest-rail">
|
</div>
|
||||||
{latest.map((item, idx) => (
|
</div>
|
||||||
<button
|
</article>
|
||||||
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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="dot-row">
|
||||||
|
{latest.map((_, idx) => (
|
||||||
|
<span key={idx} className={`dot ${idx === activeIndex ? 'active' : ''}`} onClick={() => setActiveIndex(idx)} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue