SPB Git forge
38commits 1branches 0releases
338.7 MBsize
maindefault branch
5 h agolast push
HTML 53.9% TypeScript 44.5% JavaScript 0.6% SQL 0.5%
2.6 KB · 56 lines tsx
Raw Blame History
1"use client";23import dynamic from "next/dynamic";4import Link from "next/link";5import { useEffect, useRef, useState } from "react";6import { cn } from "@/components/ui/cn";7import { Skeleton } from "@/components/ui/skeleton";8import { routes } from "@/lib/routes";910const MapCanvas = dynamic(() => import("@/components/map/map-canvas").then((m) => m.MapCanvas), { ssr: false, loading: () => <Skeleton className="h-full w-full rounded-none" /> });1112/**13 * Home: live global map (clusters at zoom ~1.4), lazy-loaded when scrolled into view; the whole surface links to /map.14 * `interactive` lets the hero map pan/zoom while the corner button keeps the way into the full map.15 */16export function MapPreview({ className, interactive, children }: { className?: string; interactive?: boolean; children?: React.ReactNode }) {17  const ref = useRef<HTMLDivElement>(null);18  const [visible, setVisible] = useState(false);19  useEffect(() => {20    const el = ref.current;21    if (!el) return;22    if (typeof IntersectionObserver === "undefined") {23      const t = setTimeout(() => setVisible(true), 0);24      return () => clearTimeout(t);25    }26    const io = new IntersectionObserver(27      (entries) => {28        if (entries.some((e) => e.isIntersecting)) {29          setVisible(true);30          io.disconnect();31        }32      },33      { rootMargin: "200px" },34    );35    io.observe(el);36    return () => io.disconnect();37  }, []);38  return (39    <div ref={ref} className={cn("relative w-full overflow-hidden rounded-sm border border-hair bg-surface-2", className ?? "h-[260px] sm:h-[380px]")}>40      {visible ? <MapCanvas preview interactivePreview={interactive} className="h-full w-full" /> : <Skeleton className="h-full w-full rounded-none" />}41      {children}42      {interactive ? (43        <Link href={routes.map()} aria-label="Open the interactive map" className="absolute bottom-3 right-3 z-[2] inline-flex h-10 items-center gap-1.5 rounded-sm border border-hair-2 bg-surface px-3 text-[12.5px] font-medium text-ink outline-none hover:border-ink focus-visible:ring-2 focus-visible:ring-accent sm:h-9">44          Open interactive map <span aria-hidden>→</span>45        </Link>46      ) : (47        <Link href={routes.map()} aria-label="Open the interactive map" className="absolute inset-0 z-[2] flex items-end justify-end p-3 outline-none focus-visible:ring-2 focus-visible:ring-accent">48          <span className="inline-flex h-9 items-center gap-1.5 rounded-sm border border-hair-2 bg-surface px-3 text-[12.5px] font-medium text-ink">49            Open interactive map <span aria-hidden>→</span>50          </span>51        </Link>52      )}53    </div>54  );55}56