feat: Heritage Globe - 中国文物全球分布地图
- Next.js + MapLibre GL 3D 地球 - CARTO dark-matter vector 底图 - 动态星空背景(闪烁、漂移、流星) - 经纬网格线(graticule) - 文物数据图层与朝代/类别筛选 - 中国风信息弹窗 - 中英双语 i18n - LayerPanel + MapControls 浮动面板
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { Search, Target, MapPin, Plus, Minus, X } from 'lucide-react';
|
||||
|
||||
interface MapControlsProps {
|
||||
onSearch?: (query: string) => void;
|
||||
onZoomIn?: () => void;
|
||||
onZoomOut?: () => void;
|
||||
onLocate?: () => void;
|
||||
onSpotlight?: () => void;
|
||||
}
|
||||
|
||||
export function MapControls({
|
||||
onSearch,
|
||||
onZoomIn,
|
||||
onZoomOut,
|
||||
onLocate,
|
||||
onSpotlight,
|
||||
}: MapControlsProps) {
|
||||
const t = useTranslations('map');
|
||||
const [searchOpen, setSearchOpen] = useState(false);
|
||||
const [query, setQuery] = useState('');
|
||||
|
||||
const submitSearch = () => {
|
||||
if (onSearch && query.trim()) {
|
||||
onSearch(query.trim());
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="absolute top-16 left-4 z-10 flex flex-col gap-2">
|
||||
{/* Search */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<button
|
||||
onClick={() => setSearchOpen(!searchOpen)}
|
||||
className="w-9 h-9 rounded-lg bg-[#06060c]/90 backdrop-blur-md border border-[var(--map-border)] flex items-center justify-center text-[var(--map-text-muted)] hover:text-white hover:bg-white/10 transition-colors shadow-lg"
|
||||
aria-label="Open search"
|
||||
>
|
||||
{searchOpen ? <X className="w-4 h-4" /> : <Search className="w-4 h-4" />}
|
||||
</button>
|
||||
|
||||
{searchOpen && (
|
||||
<div className="flex items-center gap-1 p-1 rounded-lg bg-[#06060c]/90 backdrop-blur-md border border-[var(--map-border)] shadow-lg">
|
||||
<input
|
||||
type="text"
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
onKeyDown={(e) => e.key === 'Enter' && submitSearch()}
|
||||
placeholder={t('search')}
|
||||
className="w-48 px-2 py-1.5 bg-transparent text-sm text-[var(--map-text)] placeholder:text-[var(--map-text-muted)] focus:outline-none"
|
||||
autoFocus
|
||||
/>
|
||||
<button
|
||||
onClick={submitSearch}
|
||||
className="w-7 h-7 rounded-md bg-[var(--map-panel)] hover:bg-[var(--map-panel-hover)] flex items-center justify-center text-[var(--map-text-muted)] hover:text-white transition-colors"
|
||||
>
|
||||
<Search className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Spotlight selection */}
|
||||
<button
|
||||
onClick={onSpotlight}
|
||||
className="w-9 h-9 rounded-lg bg-[#06060c]/90 backdrop-blur-md border border-[var(--map-border)] flex items-center justify-center text-[var(--map-text-muted)] hover:text-white hover:bg-white/10 transition-colors shadow-lg"
|
||||
aria-label="Enable spotlight selection"
|
||||
>
|
||||
<Target className="w-4 h-4" />
|
||||
</button>
|
||||
|
||||
{/* Zoom to my location */}
|
||||
<button
|
||||
onClick={onLocate}
|
||||
className="w-9 h-9 rounded-lg bg-[#06060c]/90 backdrop-blur-md border border-[var(--map-border)] flex items-center justify-center text-[var(--map-text-muted)] hover:text-white hover:bg-white/10 transition-colors shadow-lg"
|
||||
aria-label="Zoom to my location"
|
||||
>
|
||||
<MapPin className="w-4 h-4" />
|
||||
</button>
|
||||
|
||||
{/* Zoom in/out */}
|
||||
<div className="flex flex-col rounded-lg bg-[#06060c]/90 backdrop-blur-md border border-[var(--map-border)] shadow-lg overflow-hidden">
|
||||
<button
|
||||
onClick={onZoomIn}
|
||||
className="w-9 h-9 flex items-center justify-center text-[var(--map-text-muted)] hover:text-white hover:bg-white/10 transition-colors"
|
||||
aria-label={t('zoomIn')}
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
</button>
|
||||
<div className="h-px bg-[var(--map-border)]" />
|
||||
<button
|
||||
onClick={onZoomOut}
|
||||
className="w-9 h-9 flex items-center justify-center text-[var(--map-text-muted)] hover:text-white hover:bg-white/10 transition-colors"
|
||||
aria-label={t('zoomOut')}
|
||||
>
|
||||
<Minus className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user