No description
|
|
||
|---|---|---|
| .forgejo/workflows | ||
| src | ||
| .gitignore | ||
| eslint.config.js | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
@lilith/ui-map
Self-hosted MapLibre GL map components for creator discovery. Fully self-hostable with PMTiles support, no external API keys required.
Features
- Self-hosted capable - uses OpenStreetMap tiles or PMTiles for fully local operation
- Creator markers with avatars and popups
- Neighborhood boundaries with hover effects and click handlers
- Clustering - automatic grouping of nearby markers when zoomed out
- Radius overlay - visual circle showing search radius
- Navigation controls - zoom and compass
- Viewport tracking - get bounds on pan/zoom for lazy loading
- Custom popups - render custom content on marker click
Installation
pnpm add @lilith/ui-map
Peer Dependencies
{
"react": ">=18.0.0",
"react-dom": ">=18.0.0",
"styled-components": ">=6.0.0"
}
Usage
Basic Map
import { MapView, type MapMarker } from '@lilith/ui-map';
const markers: MapMarker[] = [
{
id: '1',
location: { lat: 40.7128, lng: -74.006 },
label: 'Jane Doe',
avatarUrl: 'https://example.com/avatar.jpg',
},
{
id: '2',
location: { lat: 40.7282, lng: -73.7949 },
label: 'John Smith',
},
];
function CreatorMap() {
return (
<MapView
markers={markers}
center={{ lat: 40.7128, lng: -74.006 }}
zoom={12}
onMarkerClick={(marker) => console.log('Clicked:', marker)}
/>
);
}
With Custom Popup
import { MapView } from '@lilith/ui-map';
<MapView
markers={creators}
renderPopup={(creator) => (
<div>
<img src={creator.avatarUrl} alt={creator.label} />
<h3>{creator.label}</h3>
<button onClick={() => viewProfile(creator.id)}>
View Profile
</button>
</div>
)}
/>
With Neighborhood Boundaries
import { MapView, type NeighborhoodBoundary } from '@lilith/ui-map';
const neighborhoods: NeighborhoodBoundary[] = [
{
id: 'soho',
name: 'SoHo',
slug: 'soho',
coordinates: [[[-74.005, 40.722], [-73.997, 40.722], /* ... */]],
creatorCount: 42,
centroid: { lat: 40.723, lng: -74.001 },
},
];
<MapView
markers={creators}
neighborhoods={neighborhoods}
onNeighborhoodClick={(neighborhood) => {
router.push(`/explore/${neighborhood.slug}`);
}}
neighborhoodColor="#10b981"
neighborhoodHighlightColor="#059669"
/>
With Radius Circle
<MapView
markers={creators}
center={{ lat: 40.7128, lng: -74.006 }}
radiusMiles={5}
markerColor="#3b82f6"
/>
With Clustering
<MapView
markers={creators}
enableClustering={true}
clusterRadius={120}
clusterMaxZoom={14}
/>
Viewport Change Tracking
Track map viewport for lazy loading markers:
import { MapView, type ViewportBounds } from '@lilith/ui-map';
function LazyLoadMap() {
const loadCreators = async (bounds: ViewportBounds, zoom: number) => {
const { minLat, maxLat, minLng, maxLng } = bounds;
const creators = await api.getCreatorsInBounds(bounds);
setMarkers(creators);
};
return (
<MapView
markers={markers}
onViewportChange={loadCreators}
/>
);
}
Self-Hosted PMTiles
Use locally hosted map tiles:
import {
MapView,
registerPMTilesProtocol,
createPMTilesStyle,
PMTILES_SOURCES,
} from '@lilith/ui-map';
// Register PMTiles protocol (call once on app init)
registerPMTilesProtocol();
// Create style for local tiles
const localStyle = createPMTilesStyle(PMTILES_SOURCES.nyc);
<MapView
markers={creators}
mapStyle={localStyle}
/>
Using Different Map Styles
import { MapView, ONLINE_STYLES, getMapStyle } from '@lilith/ui-map';
// Use predefined online styles
<MapView mapStyle={ONLINE_STYLES.positron} markers={markers} />
<MapView mapStyle={ONLINE_STYLES.darkMatter} markers={markers} />
// Or get style dynamically
const style = getMapStyle('positron');
<MapView mapStyle={style} markers={markers} />
API Reference
MapViewProps
| Prop | Type | Default | Description |
|---|---|---|---|
markers |
MapMarker[] |
[] |
Array of markers to display |
center |
MapLocation |
First marker or NYC | Map center coordinates |
zoom |
number |
12 |
Initial zoom level |
radiusMiles |
number |
- | Radius circle overlay |
onMarkerClick |
(marker) => void |
- | Marker click handler |
renderPopup |
(marker) => ReactNode |
- | Custom popup renderer |
mapStyle |
string |
CARTO Positron | MapLibre style URL |
minHeight |
string |
'600px' |
Minimum map height |
markerColor |
string |
'#3b82f6' |
Marker pin color |
neighborhoods |
NeighborhoodBoundary[] |
- | Neighborhood polygons |
onNeighborhoodClick |
(neighborhood) => void |
- | Neighborhood click handler |
neighborhoodColor |
string |
'#10b981' |
Neighborhood fill color |
neighborhoodHighlightColor |
string |
'#059669' |
Hover highlight color |
enableClustering |
boolean |
true |
Enable marker clustering |
clusterRadius |
number |
120 |
Cluster radius in pixels |
clusterMaxZoom |
number |
14 |
Max zoom for clustering |
onViewportChange |
(bounds, zoom) => void |
- | Viewport change handler |
MapMarker
interface MapMarker {
id: string;
location: MapLocation;
label?: string;
avatarUrl?: string;
}
MapLocation
interface MapLocation {
lat: number;
lng: number;
}
NeighborhoodBoundary
interface NeighborhoodBoundary {
id: string;
name: string;
slug: string;
coordinates: [number, number][][]; // GeoJSON polygon coordinates
creatorCount?: number;
centroid?: MapLocation;
}
ViewportBounds
interface ViewportBounds {
minLat: number;
maxLat: number;
minLng: number;
maxLng: number;
}
PMTiles Utilities
registerPMTilesProtocol
Register the PMTiles protocol handler for MapLibre:
import { registerPMTilesProtocol } from '@lilith/ui-map';
// Call once during app initialization
registerPMTilesProtocol();
createPMTilesStyle
Create a MapLibre style for PMTiles source:
import { createPMTilesStyle, PMTILES_SOURCES } from '@lilith/ui-map';
const style = createPMTilesStyle(PMTILES_SOURCES.nyc);
// or with custom URL
const style = createPMTilesStyle('https://example.com/tiles.pmtiles');
Map Styles
import { ONLINE_STYLES, LOCAL_PMTILES, getMapStyle } from '@lilith/ui-map';
// Online styles (CDN-hosted)
ONLINE_STYLES.positron // Light theme
ONLINE_STYLES.darkMatter // Dark theme
ONLINE_STYLES.voyager // Colorful
// Local PMTiles regions
LOCAL_PMTILES.nyc
LOCAL_PMTILES.la
LOCAL_PMTILES.sf
Types
import type {
MapViewProps,
MapMarker,
MapLocation,
NeighborhoodBoundary,
ViewportBounds,
PMTilesRegion,
OnlineStyle,
LocalRegion,
} from '@lilith/ui-map';
Browser Requirements
- WebGL support required for MapLibre GL
- Modern browsers (Chrome 80+, Firefox 75+, Safari 14+)
Attribution
The default map style uses OpenStreetMap data. Include proper attribution:
- OpenStreetMap contributors
- CARTO (for default basemap style)
License
MIT