skills/mapbox-store-locator-patterns/references/variations-react.md

138 lines
3.3 KiB
Markdown

# Variations & React Implementation
## Mobile-First Layout
```css
/* Mobile first: stack sidebar on top */
@media (max-width: 768px) {
#app {
flex-direction: column;
}
.sidebar {
width: 100%;
height: 40vh;
max-height: 40vh;
}
#map {
height: 60vh;
}
/* Toggle sidebar */
.sidebar.collapsed {
height: 60px;
}
}
```
## Fullscreen Map with Overlay
```javascript
// Map takes full screen, list appears as overlay
const listOverlay = document.createElement('div');
listOverlay.className = 'list-overlay';
listOverlay.innerHTML = `
<button id="toggle-list">View All Locations (${stores.features.length})</button>
<div id="listings" class="hidden"></div>
`;
document.getElementById('toggle-list').addEventListener('click', () => {
document.getElementById('listings').classList.toggle('hidden');
});
```
## Map-Only View
```javascript
// No sidebar, everything in popups
function createDetailedPopup(store) {
const popup = new mapboxgl.Popup({ maxWidth: '400px' })
.setLngLat(store.geometry.coordinates)
.setHTML(
`
<div class="store-popup">
<h3>${store.properties.name}</h3>
<p class="address">${store.properties.address}</p>
<p class="phone">${store.properties.phone}</p>
<p class="hours">${store.properties.hours}</p>
${store.properties.distance ? `<p class="distance">${store.properties.distance} mi away</p>` : ''}
<div class="actions">
<button onclick="getDirections('${store.properties.id}')">Directions</button>
<button onclick="callStore('${store.properties.phone}')">Call</button>
${store.properties.website ? `<a href="${store.properties.website}" target="_blank">Website</a>` : ''}
</div>
</div>
`
)
.addTo(map);
}
```
## React Implementation
```jsx
import { useEffect, useRef, useState } from 'react';
import mapboxgl from 'mapbox-gl';
function StoreLocator({ stores }) {
const mapContainer = useRef(null);
const map = useRef(null);
const [selectedStore, setSelectedStore] = useState(null);
const [filteredStores, setFilteredStores] = useState(stores);
useEffect(() => {
if (map.current) return;
map.current = new mapboxgl.Map({
container: mapContainer.current,
style: 'mapbox://styles/mapbox/standard',
center: [-77.034084, 38.909671],
zoom: 11
});
map.current.on('load', () => {
map.current.addSource('stores', {
type: 'geojson',
data: filteredStores
});
map.current.addLayer({
id: 'stores',
type: 'circle',
source: 'stores',
paint: {
'circle-color': '#2196f3',
'circle-radius': 8
}
});
map.current.on('click', 'stores', (e) => {
setSelectedStore(e.features[0]);
});
});
return () => map.current.remove();
}, []);
// Update source when filtered stores change
useEffect(() => {
if (map.current && map.current.getSource('stores')) {
map.current.getSource('stores').setData(filteredStores);
}
}, [filteredStores]);
return (
<div className="store-locator">
<Sidebar
stores={filteredStores}
selectedStore={selectedStore}
onStoreClick={setSelectedStore}
onFilter={setFilteredStores}
/>
<div ref={mapContainer} className="map-container" />
</div>
);
}
```