mehdi-akbari-map 1.0.6 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/InteractiveMap.d.ts.map +1 -1
- package/dist/components/Map.d.ts +0 -1
- package/dist/components/Map.d.ts.map +1 -1
- package/dist/components/StoreCard.d.ts.map +1 -1
- package/dist/index.cjs +48 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +25 -25
- package/dist/index.css.map +1 -1
- package/dist/index.js +48 -21
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +48 -21
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +48 -21
- package/dist/react.js.map +1 -1
- package/dist/styles.css +36 -37
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InteractiveMap.d.ts","sourceRoot":"","sources":["../../src/components/InteractiveMap.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAmD,MAAM,OAAO,CAAC;AAGxE,OAAO,EAAS,QAAQ,EAAE,MAAM,UAAU,CAAC;AAE3C,eAAO,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC,QAAQ,
|
|
1
|
+
{"version":3,"file":"InteractiveMap.d.ts","sourceRoot":"","sources":["../../src/components/InteractiveMap.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAmD,MAAM,OAAO,CAAC;AAGxE,OAAO,EAAS,QAAQ,EAAE,MAAM,UAAU,CAAC;AAE3C,eAAO,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC,QAAQ,CAyF7C,CAAC"}
|
package/dist/components/Map.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Map.d.ts","sourceRoot":"","sources":["../../src/components/Map.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAmD,MAAM,OAAO,CAAC;AACxE,OAAO,KAAK,EAAoB,QAAQ,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"Map.d.ts","sourceRoot":"","sources":["../../src/components/Map.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAmD,MAAM,OAAO,CAAC;AACxE,OAAO,KAAK,EAAoB,QAAQ,EAAE,MAAM,UAAU,CAAC;AAM3D,QAAA,MAAM,GAAG,EAAE,KAAK,CAAC,EAAE,CAAC,QAAQ,CA2J3B,CAAC;AAEF,eAAe,GAAG,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StoreCard.d.ts","sourceRoot":"","sources":["../../src/components/StoreCard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAEjC,UAAU,cAAc;IACtB,KAAK,EAAE,KAAK,CAAC;IACb,UAAU,EAAE,OAAO,CAAC;IACpB,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,eAAO,MAAM,SAAS,EAAE,KAAK,CAAC,EAAE,CAAC,cAAc,
|
|
1
|
+
{"version":3,"file":"StoreCard.d.ts","sourceRoot":"","sources":["../../src/components/StoreCard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAEjC,UAAU,cAAc;IACtB,KAAK,EAAE,KAAK,CAAC;IACb,UAAU,EAAE,OAAO,CAAC;IACpB,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,eAAO,MAAM,SAAS,EAAE,KAAK,CAAC,EAAE,CAAC,cAAc,CA6D9C,CAAC"}
|
package/dist/index.cjs
CHANGED
|
@@ -105,7 +105,6 @@ function createClusterElement(count) {
|
|
|
105
105
|
|
|
106
106
|
// src/components/Map.tsx
|
|
107
107
|
var import_supercluster = __toESM(require("supercluster"), 1);
|
|
108
|
-
var import_NeshanMapboxGl = require("@neshan-maps-platform/mapbox-gl/dist/NeshanMapboxGl.css");
|
|
109
108
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
110
109
|
var Map = ({
|
|
111
110
|
options,
|
|
@@ -243,6 +242,9 @@ var Map_default = Map;
|
|
|
243
242
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
244
243
|
var StoreCard = ({ store, isSelected, onSelect }) => {
|
|
245
244
|
const googleMapUrl = `https://www.google.com/maps/dir/?api=1&destination=${store.lat},${store.lng}`;
|
|
245
|
+
const handleActionClick = (e) => {
|
|
246
|
+
e.stopPropagation();
|
|
247
|
+
};
|
|
246
248
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
247
249
|
"div",
|
|
248
250
|
{
|
|
@@ -262,9 +264,37 @@ var StoreCard = ({ store, isSelected, onSelect }) => {
|
|
|
262
264
|
] }),
|
|
263
265
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "store-desc", children: store.desc }),
|
|
264
266
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "store-details-wrapper", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "store-details-content", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "action-buttons", children: [
|
|
265
|
-
store.phone && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
266
|
-
|
|
267
|
-
|
|
267
|
+
store.phone && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
268
|
+
"a",
|
|
269
|
+
{
|
|
270
|
+
href: `tel:${store.phone}`,
|
|
271
|
+
className: "btn btn-call",
|
|
272
|
+
onClick: handleActionClick,
|
|
273
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: "\u062A\u0645\u0627\u0633" })
|
|
274
|
+
}
|
|
275
|
+
),
|
|
276
|
+
store.whatsapp && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
277
|
+
"a",
|
|
278
|
+
{
|
|
279
|
+
href: `https://wa.me/${store.whatsapp}`,
|
|
280
|
+
target: "_blank",
|
|
281
|
+
rel: "noreferrer",
|
|
282
|
+
className: "btn btn-wa",
|
|
283
|
+
onClick: handleActionClick,
|
|
284
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: "\u0648\u0627\u062A\u0633\u0627\u067E" })
|
|
285
|
+
}
|
|
286
|
+
),
|
|
287
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
288
|
+
"a",
|
|
289
|
+
{
|
|
290
|
+
href: googleMapUrl,
|
|
291
|
+
target: "_blank",
|
|
292
|
+
rel: "noreferrer",
|
|
293
|
+
className: "btn btn-route",
|
|
294
|
+
onClick: handleActionClick,
|
|
295
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: "\u0645\u0633\u06CC\u0631\u06CC\u0627\u0628\u06CC" })
|
|
296
|
+
}
|
|
297
|
+
)
|
|
268
298
|
] }) }) })
|
|
269
299
|
]
|
|
270
300
|
}
|
|
@@ -285,23 +315,22 @@ var InteractiveMap = (props) => {
|
|
|
285
315
|
}, []);
|
|
286
316
|
(0, import_react2.useEffect)(() => {
|
|
287
317
|
if (isMobile && props.selectedMarkerId && sliderRef.current) {
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
selectedCard
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
318
|
+
const timer = setTimeout(() => {
|
|
319
|
+
const selectedCard = sliderRef.current?.querySelector(`[data-id="${props.selectedMarkerId}"]`);
|
|
320
|
+
if (selectedCard) {
|
|
321
|
+
selectedCard.scrollIntoView({
|
|
322
|
+
behavior: "smooth",
|
|
323
|
+
inline: "center",
|
|
324
|
+
block: "nearest"
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
}, 100);
|
|
328
|
+
return () => clearTimeout(timer);
|
|
296
329
|
}
|
|
297
330
|
}, [props.selectedMarkerId, isMobile]);
|
|
298
331
|
const handleStoreSelect = (0, import_react2.useCallback)((store, index) => {
|
|
299
|
-
if (
|
|
300
|
-
|
|
301
|
-
return;
|
|
302
|
-
}
|
|
303
|
-
if (props.onMarkerClick) {
|
|
304
|
-
props.onMarkerClick(store, index, mapInstance);
|
|
332
|
+
if (mapInstance) {
|
|
333
|
+
props.onMarkerClick?.(store, index, mapInstance);
|
|
305
334
|
}
|
|
306
335
|
}, [mapInstance, props]);
|
|
307
336
|
const safeMarkers = props.markers || [];
|
|
@@ -326,9 +355,7 @@ var InteractiveMap = (props) => {
|
|
|
326
355
|
onMapLoad: (map) => {
|
|
327
356
|
if (map) {
|
|
328
357
|
setMapInstance(map);
|
|
329
|
-
|
|
330
|
-
props.onMapLoad(map);
|
|
331
|
-
}
|
|
358
|
+
props.onMapLoad?.(map);
|
|
332
359
|
}
|
|
333
360
|
}
|
|
334
361
|
}
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/components/InteractiveMap.tsx","../src/components/Map.tsx","../src/utils/createCustomMarkerElement.ts","../src/utils/createClusterElement.ts","../src/components/StoreCard.tsx"],"sourcesContent":["\r\nimport './styles.css';\r\n\r\n\r\nexport { InteractiveMap as NeshanMap } from './components/InteractiveMap';\r\n\r\n\r\nexport { default as RawMap } from './components/Map';\r\n\r\n\r\nexport type {\r\n MapProps,\r\n MapOptions,\r\n MarkerData,\r\n Store,\r\n MapboxMap,\r\n LngLatLike,\r\n} from './types';","\"use client\";\r\n\r\nimport React, { useState, useEffect, useRef, useCallback } from 'react';\r\nimport Map from './Map';\r\nimport { StoreCard } from './StoreCard';\r\nimport { Store, MapProps } from '../types';\r\n\r\nexport const InteractiveMap: React.FC<MapProps> = (props) => {\r\n const [isMobile, setIsMobile] = useState(false);\r\n const [mapInstance, setMapInstance] = useState<any>(null);\r\n const sliderRef = useRef<HTMLDivElement>(null);\r\n\r\n \r\n useEffect(() => {\r\n const checkMobile = () => setIsMobile(window.innerWidth < 1024);\r\n checkMobile();\r\n window.addEventListener('resize', checkMobile);\r\n return () => window.removeEventListener('resize', checkMobile);\r\n }, []);\r\n\r\n \r\n useEffect(() => {\r\n if (isMobile && props.selectedMarkerId && sliderRef.current) {\r\n const selectedCard = sliderRef.current.querySelector(`[data-id=\"${props.selectedMarkerId}\"]`);\r\n if (selectedCard) {\r\n selectedCard.scrollIntoView({\r\n behavior: 'smooth',\r\n inline: 'center',\r\n block: 'nearest'\r\n });\r\n }\r\n }\r\n }, [props.selectedMarkerId, isMobile]);\r\n\r\n \r\n const handleStoreSelect = useCallback((store: Store, index: number) => {\r\n \r\n if (!mapInstance) {\r\n console.warn(\"نقشه هنوز آماده نشده است.\");\r\n return;\r\n }\r\n \r\n \r\n if (props.onMarkerClick) {\r\n props.onMarkerClick(store, index, mapInstance);\r\n }\r\n }, [mapInstance, props]);\r\n\r\n const safeMarkers = props.markers || [];\r\n\r\n return (\r\n <div className={`map-layout-root ${isMobile ? 'is-mobile' : 'is-desktop'}`}>\r\n \r\n {/* سایدبار دسکتاپ */}\r\n {!isMobile && (\r\n <aside className=\"sidebar\">\r\n <header className=\"sidebar-header\">لیست فروشگاهها</header>\r\n <div className=\"sidebar-list\">\r\n {safeMarkers.map((store, idx) => (\r\n <StoreCard \r\n key={store.id} \r\n store={store} \r\n isSelected={props.selectedMarkerId === store.id}\r\n onSelect={() => handleStoreSelect(store, idx)}\r\n />\r\n ))}\r\n </div>\r\n </aside>\r\n )}\r\n\r\n {/* بخش نقشه */}\r\n <main className=\"map-container\">\r\n <Map \r\n {...props} \r\n onMapLoad={(map) => {\r\n \r\n if (map) {\r\n setMapInstance(map);\r\n if (props.onMapLoad) {\r\n props.onMapLoad(map);\r\n }\r\n }\r\n }} \r\n />\r\n \r\n {/* باج محصول */}\r\n {props.productName && (\r\n <div className=\"product-badge\">\r\n <span>{props.productName}</span>\r\n </div>\r\n )}\r\n\r\n {/* اسلایدر موبایل */}\r\n {isMobile && (\r\n <div className=\"mobile-slider\" ref={sliderRef}>\r\n {safeMarkers.map((store, idx) => (\r\n <StoreCard \r\n key={store.id} \r\n store={store} \r\n isSelected={props.selectedMarkerId === store.id}\r\n onSelect={() => handleStoreSelect(store, idx)}\r\n />\r\n ))}\r\n </div>\r\n )}\r\n </main>\r\n </div>\r\n );\r\n};","\"use client\";\r\n\r\nimport React, { useEffect, useRef, useState, useCallback } from 'react';\r\nimport type { MapboxMap, Store, MapProps } from '../types';\r\nimport { createCustomMarkerElement } from '../utils/createCustomMarkerElement';\r\nimport { createClusterElement } from '../utils/createClusterElement';\r\nimport Supercluster from 'supercluster';\r\n\r\nimport '@neshan-maps-platform/mapbox-gl/dist/NeshanMapboxGl.css';\r\n\r\nconst Map: React.FC<MapProps> = ({\r\n options,\r\n markers = [],\r\n markerLogoUrl = '',\r\n selectedMarkerId = null,\r\n onMarkerClick,\r\n onMapLoad,\r\n style = { width: '100%', height: '100%' },\r\n}) => {\r\n const mapContainerRef = useRef<HTMLDivElement>(null);\r\n const [mapInstance, setMapInstance] = useState<MapboxMap | null>(null);\r\n const [mapLib, setMapLib] = useState<any>(null);\r\n const markersRef = useRef<any[]>([]);\r\n const isRemoving = useRef(false); \r\n \r\n const clusterIndex = useRef(new Supercluster({\r\n radius: 60,\r\n maxZoom: 16\r\n }));\r\n\r\n \r\n useEffect(() => {\r\n let mounted = true;\r\n import('@neshan-maps-platform/mapbox-gl').then((mod) => {\r\n if (mounted) setMapLib(mod.default || mod);\r\n });\r\n return () => { mounted = false; };\r\n }, []);\r\n\r\n \r\n useEffect(() => {\r\n if (!mapLib || !mapContainerRef.current || mapInstance) return;\r\n\r\n const map = new mapLib.Map({\r\n container: mapContainerRef.current,\r\n ...options,\r\n });\r\n\r\n map.on('load', () => {\r\n if (!isRemoving.current) {\r\n setMapInstance(map);\r\n onMapLoad?.(map);\r\n }\r\n });\r\n\r\n return () => {\r\n if (map) {\r\n isRemoving.current = true;\r\n \r\n setTimeout(() => {\r\n try {\r\n map.remove();\r\n } catch (e) {\r\n console.warn(\"Mapbox remove error ignored:\", e);\r\n }\r\n }, 0);\r\n }\r\n };\r\n \r\n }, [mapLib]); \r\n\r\n \r\n useEffect(() => {\r\n if (!mapInstance || !selectedMarkerId || isRemoving.current) return;\r\n const selectedStore = markers.find(m => m.id === selectedMarkerId);\r\n if (selectedStore) {\r\n mapInstance.flyTo({\r\n center: [selectedStore.lng, selectedStore.lat],\r\n zoom: 16, \r\n essential: true,\r\n duration: 1200\r\n });\r\n }\r\n }, [selectedMarkerId, mapInstance, markers]);\r\n\r\n \r\n const updateMarkers = useCallback(() => {\r\n if (!mapInstance || !mapLib || isRemoving.current) return;\r\n\r\n try {\r\n const bounds = mapInstance.getBounds();\r\n const zoom = Math.floor(mapInstance.getZoom());\r\n\r\n const clusters = clusterIndex.current.getClusters(\r\n [bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth()],\r\n zoom\r\n );\r\n\r\n markersRef.current.forEach(m => m.remove());\r\n markersRef.current = [];\r\n\r\n clusters.forEach(cluster => {\r\n const [lng, lat] = cluster.geometry.coordinates;\r\n const { cluster: isCluster, point_count: count } = cluster.properties;\r\n\r\n let el: HTMLElement;\r\n\r\n if (isCluster) {\r\n el = createClusterElement(count);\r\n el.onclick = (e) => {\r\n e.stopPropagation();\r\n const expansionZoom = Math.min(\r\n clusterIndex.current.getClusterExpansionZoom(cluster.id as number),\r\n 18\r\n );\r\n mapInstance.easeTo({ center: [lng, lat], zoom: expansionZoom });\r\n };\r\n } else {\r\n const storeData = cluster.properties as Store;\r\n el = createCustomMarkerElement({\r\n marker: storeData,\r\n isSelected: storeData.id === selectedMarkerId,\r\n logoSrc: markerLogoUrl,\r\n });\r\n el.onclick = () => onMarkerClick?.(storeData, 0, mapInstance);\r\n }\r\n\r\n const newMarker = new mapLib.Marker({ element: el, anchor: isCluster ? 'center' : 'bottom' })\r\n .setLngLat([lng, lat])\r\n .addTo(mapInstance);\r\n \r\n markersRef.current.push(newMarker);\r\n });\r\n } catch (err) {\r\n console.error(\"Update markers failed:\", err);\r\n }\r\n }, [mapInstance, mapLib, selectedMarkerId, markerLogoUrl, onMarkerClick]);\r\n\r\n \r\n useEffect(() => {\r\n if (isRemoving.current) return;\r\n const points: any = markers.map(s => ({\r\n type: 'Feature',\r\n properties: { ...s },\r\n geometry: { type: 'Point', coordinates: [s.lng, s.lat] }\r\n }));\r\n clusterIndex.current.load(points);\r\n updateMarkers();\r\n }, [markers, updateMarkers]);\r\n\r\n \r\n useEffect(() => {\r\n if (!mapInstance || isRemoving.current) return;\r\n mapInstance.on('moveend', updateMarkers);\r\n return () => { mapInstance.off('moveend', updateMarkers); };\r\n }, [mapInstance, updateMarkers]);\r\n\r\n return (\r\n <div style={{ position: 'relative', overflow: 'hidden', ...style }}>\r\n <div \r\n ref={mapContainerRef} \r\n style={{ width: '100%', height: '100%', backfaceVisibility: 'hidden' }} \r\n />\r\n </div>\r\n );\r\n};\r\n\r\nexport default Map;","import type { MarkerData } from '../types';\r\n\r\ninterface CreateMarkerOptions {\r\n marker: MarkerData;\r\n isSelected: boolean;\r\n logoSrc?: string;\r\n showPrice?: boolean;\r\n}\r\n\r\nexport function createCustomMarkerElement({\r\n marker,\r\n isSelected,\r\n logoSrc = '',\r\n showPrice = true,\r\n}: CreateMarkerOptions): HTMLElement {\r\n \r\n const container = document.createElement('div');\r\n container.className = 'neshan-marker-container';\r\n\r\n \r\n const body = document.createElement('div');\r\n body.className = `neshan-marker-body ${isSelected ? 'neshan-marker-selected' : ''}`;\r\n \r\n \r\n Object.assign(body.style, {\r\n display: 'flex',\r\n flexDirection: 'column',\r\n alignItems: 'center',\r\n transition: 'transform 0.3s ease',\r\n transform: isSelected ? 'scale(1.2)' : 'scale(1)',\r\n });\r\n\r\n \r\n const iconBox = document.createElement('div');\r\n Object.assign(iconBox.style, {\r\n width: '36px',\r\n height: '36px',\r\n borderRadius: '50%',\r\n\r\n border: isSelected ? '3px solid #3b82f6' : '2px solid #ef4444',\r\n boxShadow: '0 4px 8px rgba(0,0,0,0.2)',\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n overflow: 'hidden'\r\n });\r\n\r\n if (logoSrc) {\r\n const img = document.createElement('img');\r\n img.src = logoSrc;\r\n img.style.width = '100%';\r\n img.style.height = '100%';\r\n img.style.objectFit = 'cover';\r\n iconBox.appendChild(img);\r\n } else {\r\n iconBox.style.backgroundColor = '#ef4444';\r\n }\r\n\r\n body.appendChild(iconBox);\r\n\r\n \r\n if (showPrice && (marker.price || marker.name)) {\r\n const label = document.createElement('div');\r\n label.className = 'neshan-marker-label'; // استفاده از کلاس CSS جدید\r\n label.textContent = marker.price ? `${marker.price} ت` : (marker.name ?? '');\r\n body.appendChild(label);\r\n}\r\n\r\n container.appendChild(body);\r\n return container;\r\n}","export function createClusterElement(count: number): HTMLElement {\r\n const container = document.createElement('div');\r\n container.className = 'neshan-cluster-marker';\r\n \r\n \r\n const countSpan = document.createElement('span');\r\n countSpan.className = 'neshan-cluster-count';\r\n countSpan.innerText = count.toString();\r\n \r\n container.appendChild(countSpan);\r\n return container;\r\n}","import React from 'react';\r\nimport { Store } from '../types';\r\n\r\ninterface StoreCardProps {\r\n store: Store;\r\n isSelected: boolean;\r\n onSelect: () => void;\r\n}\r\n\r\nexport const StoreCard: React.FC<StoreCardProps> = ({ store, isSelected, onSelect }) => {\r\n const googleMapUrl = `https://www.google.com/maps/dir/?api=1&destination=${store.lat},${store.lng}`;\r\n\r\n return (\r\n <div \r\n className={`store-card ${isSelected ? 'active' : ''}`}\r\n onClick={onSelect}\r\n data-id={store.id} // برای پیدا کردن کارت در اسکرول موبایل\r\n >\r\n <div className=\"store-card-header\">\r\n <div>\r\n <h4 className=\"store-name\">{store.name}</h4>\r\n <span className=\"store-city\">{store.city}</span>\r\n </div>\r\n <div className=\"store-price\">{store.price} تومان</div>\r\n </div>\r\n\r\n <p className=\"store-desc\">{store.desc}</p>\r\n\r\n {/* بخش آکاردئونی با CSS Animation */}\r\n <div className=\"store-details-wrapper\">\r\n <div className=\"store-details-content\">\r\n <div className=\"action-buttons\">\r\n {store.phone && (\r\n <a href={`tel:${store.phone}`} className=\"btn btn-call\" onClick={(e) => e.stopPropagation()}>\r\n <span>تماس</span>\r\n </a>\r\n )}\r\n {store.whatsapp && (\r\n <a href={`https://wa.me/${store.whatsapp}`} target=\"_blank\" className=\"btn btn-wa\" onClick={(e) => e.stopPropagation()}>\r\n <span>واتساپ</span>\r\n </a>\r\n )}\r\n <a href={googleMapUrl} target=\"_blank\" className=\"btn btn-route\" onClick={(e) => e.stopPropagation()}>\r\n <span>مسیریابی</span>\r\n </a>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n );\r\n};"],"mappings":"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","names":["import_react","Supercluster","import_jsx_runtime","import_jsx_runtime"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/components/InteractiveMap.tsx","../src/components/Map.tsx","../src/utils/createCustomMarkerElement.ts","../src/utils/createClusterElement.ts","../src/components/StoreCard.tsx"],"sourcesContent":["\r\nimport './styles.css';\r\n\r\n\r\nexport { InteractiveMap as NeshanMap } from './components/InteractiveMap';\r\n\r\n\r\nexport { default as RawMap } from './components/Map';\r\n\r\n\r\nexport type {\r\n MapProps,\r\n MapOptions,\r\n MarkerData,\r\n Store,\r\n MapboxMap,\r\n LngLatLike,\r\n} from './types';","\"use client\";\r\n\r\nimport React, { useState, useEffect, useRef, useCallback } from 'react';\r\nimport Map from './Map';\r\nimport { StoreCard } from './StoreCard';\r\nimport { Store, MapProps } from '../types';\r\n\r\nexport const InteractiveMap: React.FC<MapProps> = (props) => {\r\n const [isMobile, setIsMobile] = useState(false);\r\n const [mapInstance, setMapInstance] = useState<any>(null);\r\n const sliderRef = useRef<HTMLDivElement>(null);\r\n\r\n useEffect(() => {\r\n const checkMobile = () => setIsMobile(window.innerWidth < 1024);\r\n checkMobile();\r\n window.addEventListener('resize', checkMobile);\r\n return () => window.removeEventListener('resize', checkMobile);\r\n }, []);\r\n\r\n // هماهنگی اسکرول اسلایدر موبایل با مارکر انتخاب شده\r\n useEffect(() => {\r\n if (isMobile && props.selectedMarkerId && sliderRef.current) {\r\n // استفاده از یک وقفه کوتاه برای اطمینان از پایان انیمیشن آکاردئون\r\n const timer = setTimeout(() => {\r\n const selectedCard = sliderRef.current?.querySelector(`[data-id=\"${props.selectedMarkerId}\"]`);\r\n if (selectedCard) {\r\n selectedCard.scrollIntoView({\r\n behavior: 'smooth',\r\n inline: 'center',\r\n block: 'nearest'\r\n });\r\n }\r\n }, 100);\r\n return () => clearTimeout(timer);\r\n }\r\n }, [props.selectedMarkerId, isMobile]);\r\n\r\n const handleStoreSelect = useCallback((store: Store, index: number) => {\r\n if (mapInstance) {\r\n props.onMarkerClick?.(store, index, mapInstance);\r\n }\r\n }, [mapInstance, props]);\r\n\r\n const safeMarkers = props.markers || [];\r\n\r\n return (\r\n <div className={`map-layout-root ${isMobile ? 'is-mobile' : 'is-desktop'}`}>\r\n \r\n {!isMobile && (\r\n <aside className=\"sidebar\">\r\n <header className=\"sidebar-header\">لیست فروشگاهها</header>\r\n <div className=\"sidebar-list\">\r\n {safeMarkers.map((store, idx) => (\r\n <StoreCard \r\n key={store.id} \r\n store={store} \r\n isSelected={props.selectedMarkerId === store.id}\r\n onSelect={() => handleStoreSelect(store, idx)}\r\n />\r\n ))}\r\n </div>\r\n </aside>\r\n )}\r\n\r\n <main className=\"map-container\">\r\n <Map \r\n {...props} \r\n onMapLoad={(map) => {\r\n if (map) {\r\n setMapInstance(map);\r\n props.onMapLoad?.(map);\r\n }\r\n }} \r\n />\r\n \r\n {props.productName && (\r\n <div className=\"product-badge\">\r\n <span>{props.productName}</span>\r\n </div>\r\n )}\r\n\r\n {isMobile && (\r\n <div className=\"mobile-slider\" ref={sliderRef}>\r\n {safeMarkers.map((store, idx) => (\r\n <StoreCard \r\n key={store.id} \r\n store={store} \r\n isSelected={props.selectedMarkerId === store.id}\r\n onSelect={() => handleStoreSelect(store, idx)}\r\n />\r\n ))}\r\n </div>\r\n )}\r\n </main>\r\n </div>\r\n );\r\n};","\"use client\";\r\n\r\nimport React, { useEffect, useRef, useState, useCallback } from 'react';\r\nimport type { MapboxMap, Store, MapProps } from '../types';\r\nimport { createCustomMarkerElement } from '../utils/createCustomMarkerElement';\r\nimport { createClusterElement } from '../utils/createClusterElement';\r\nimport Supercluster from 'supercluster';\r\n\r\n\r\nconst Map: React.FC<MapProps> = ({\r\n options,\r\n markers = [],\r\n markerLogoUrl = '',\r\n selectedMarkerId = null,\r\n onMarkerClick,\r\n onMapLoad,\r\n style = { width: '100%', height: '100%' },\r\n}) => {\r\n const mapContainerRef = useRef<HTMLDivElement>(null);\r\n const [mapInstance, setMapInstance] = useState<MapboxMap | null>(null);\r\n const [mapLib, setMapLib] = useState<any>(null);\r\n const markersRef = useRef<any[]>([]);\r\n const isRemoving = useRef(false); \r\n \r\n const clusterIndex = useRef(new Supercluster({\r\n radius: 60,\r\n maxZoom: 16\r\n }));\r\n\r\n \r\n useEffect(() => {\r\n let mounted = true;\r\n import('@neshan-maps-platform/mapbox-gl').then((mod) => {\r\n if (mounted) setMapLib(mod.default || mod);\r\n });\r\n return () => { mounted = false; };\r\n }, []);\r\n\r\n \r\n useEffect(() => {\r\n if (!mapLib || !mapContainerRef.current || mapInstance) return;\r\n\r\n const map = new mapLib.Map({\r\n container: mapContainerRef.current,\r\n ...options,\r\n });\r\n\r\n map.on('load', () => {\r\n if (!isRemoving.current) {\r\n setMapInstance(map);\r\n onMapLoad?.(map);\r\n }\r\n });\r\n\r\n return () => {\r\n if (map) {\r\n isRemoving.current = true;\r\n \r\n setTimeout(() => {\r\n try {\r\n map.remove();\r\n } catch (e) {\r\n console.warn(\"Mapbox remove error ignored:\", e);\r\n }\r\n }, 0);\r\n }\r\n };\r\n \r\n }, [mapLib]); \r\n\r\n \r\n useEffect(() => {\r\n if (!mapInstance || !selectedMarkerId || isRemoving.current) return;\r\n const selectedStore = markers.find(m => m.id === selectedMarkerId);\r\n if (selectedStore) {\r\n mapInstance.flyTo({\r\n center: [selectedStore.lng, selectedStore.lat],\r\n zoom: 16, \r\n essential: true,\r\n duration: 1200\r\n });\r\n }\r\n }, [selectedMarkerId, mapInstance, markers]);\r\n\r\n \r\n const updateMarkers = useCallback(() => {\r\n if (!mapInstance || !mapLib || isRemoving.current) return;\r\n\r\n try {\r\n const bounds = mapInstance.getBounds();\r\n const zoom = Math.floor(mapInstance.getZoom());\r\n\r\n const clusters = clusterIndex.current.getClusters(\r\n [bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth()],\r\n zoom\r\n );\r\n\r\n markersRef.current.forEach(m => m.remove());\r\n markersRef.current = [];\r\n\r\n clusters.forEach(cluster => {\r\n const [lng, lat] = cluster.geometry.coordinates;\r\n const { cluster: isCluster, point_count: count } = cluster.properties;\r\n\r\n let el: HTMLElement;\r\n\r\n if (isCluster) {\r\n el = createClusterElement(count);\r\n el.onclick = (e) => {\r\n e.stopPropagation();\r\n const expansionZoom = Math.min(\r\n clusterIndex.current.getClusterExpansionZoom(cluster.id as number),\r\n 18\r\n );\r\n mapInstance.easeTo({ center: [lng, lat], zoom: expansionZoom });\r\n };\r\n } else {\r\n const storeData = cluster.properties as Store;\r\n el = createCustomMarkerElement({\r\n marker: storeData,\r\n isSelected: storeData.id === selectedMarkerId,\r\n logoSrc: markerLogoUrl,\r\n });\r\n el.onclick = () => onMarkerClick?.(storeData, 0, mapInstance);\r\n }\r\n\r\n const newMarker = new mapLib.Marker({ element: el, anchor: isCluster ? 'center' : 'bottom' })\r\n .setLngLat([lng, lat])\r\n .addTo(mapInstance);\r\n \r\n markersRef.current.push(newMarker);\r\n });\r\n } catch (err) {\r\n console.error(\"Update markers failed:\", err);\r\n }\r\n }, [mapInstance, mapLib, selectedMarkerId, markerLogoUrl, onMarkerClick]);\r\n\r\n \r\n useEffect(() => {\r\n if (isRemoving.current) return;\r\n const points: any = markers.map(s => ({\r\n type: 'Feature',\r\n properties: { ...s },\r\n geometry: { type: 'Point', coordinates: [s.lng, s.lat] }\r\n }));\r\n clusterIndex.current.load(points);\r\n updateMarkers();\r\n }, [markers, updateMarkers]);\r\n\r\n \r\n useEffect(() => {\r\n if (!mapInstance || isRemoving.current) return;\r\n mapInstance.on('moveend', updateMarkers);\r\n return () => { mapInstance.off('moveend', updateMarkers); };\r\n }, [mapInstance, updateMarkers]);\r\n\r\n return (\r\n <div style={{ position: 'relative', overflow: 'hidden', ...style }}>\r\n <div \r\n ref={mapContainerRef} \r\n style={{ width: '100%', height: '100%', backfaceVisibility: 'hidden' }} \r\n />\r\n </div>\r\n );\r\n};\r\n\r\nexport default Map;","import type { MarkerData } from '../types';\r\n\r\ninterface CreateMarkerOptions {\r\n marker: MarkerData;\r\n isSelected: boolean;\r\n logoSrc?: string;\r\n showPrice?: boolean;\r\n}\r\n\r\nexport function createCustomMarkerElement({\r\n marker,\r\n isSelected,\r\n logoSrc = '',\r\n showPrice = true,\r\n}: CreateMarkerOptions): HTMLElement {\r\n \r\n const container = document.createElement('div');\r\n container.className = 'neshan-marker-container';\r\n\r\n \r\n const body = document.createElement('div');\r\n body.className = `neshan-marker-body ${isSelected ? 'neshan-marker-selected' : ''}`;\r\n \r\n \r\n Object.assign(body.style, {\r\n display: 'flex',\r\n flexDirection: 'column',\r\n alignItems: 'center',\r\n transition: 'transform 0.3s ease',\r\n transform: isSelected ? 'scale(1.2)' : 'scale(1)',\r\n });\r\n\r\n \r\n const iconBox = document.createElement('div');\r\n Object.assign(iconBox.style, {\r\n width: '36px',\r\n height: '36px',\r\n borderRadius: '50%',\r\n\r\n border: isSelected ? '3px solid #3b82f6' : '2px solid #ef4444',\r\n boxShadow: '0 4px 8px rgba(0,0,0,0.2)',\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n overflow: 'hidden'\r\n });\r\n\r\n if (logoSrc) {\r\n const img = document.createElement('img');\r\n img.src = logoSrc;\r\n img.style.width = '100%';\r\n img.style.height = '100%';\r\n img.style.objectFit = 'cover';\r\n iconBox.appendChild(img);\r\n } else {\r\n iconBox.style.backgroundColor = '#ef4444';\r\n }\r\n\r\n body.appendChild(iconBox);\r\n\r\n \r\n if (showPrice && (marker.price || marker.name)) {\r\n const label = document.createElement('div');\r\n label.className = 'neshan-marker-label'; // استفاده از کلاس CSS جدید\r\n label.textContent = marker.price ? `${marker.price} ت` : (marker.name ?? '');\r\n body.appendChild(label);\r\n}\r\n\r\n container.appendChild(body);\r\n return container;\r\n}","export function createClusterElement(count: number): HTMLElement {\r\n const container = document.createElement('div');\r\n container.className = 'neshan-cluster-marker';\r\n \r\n \r\n const countSpan = document.createElement('span');\r\n countSpan.className = 'neshan-cluster-count';\r\n countSpan.innerText = count.toString();\r\n \r\n container.appendChild(countSpan);\r\n return container;\r\n}","import React from 'react';\r\nimport { Store } from '../types';\r\n\r\ninterface StoreCardProps {\r\n store: Store;\r\n isSelected: boolean;\r\n onSelect: () => void;\r\n}\r\n\r\nexport const StoreCard: React.FC<StoreCardProps> = ({ store, isSelected, onSelect }) => {\r\n const googleMapUrl = `https://www.google.com/maps/dir/?api=1&destination=${store.lat},${store.lng}`;\r\n\r\n const handleActionClick = (e: React.MouseEvent) => {\r\n // جلوگیری از تغییر وضعیت انتخاب کارت هنگام کلیک روی دکمههای تماس/واتساپ\r\n e.stopPropagation();\r\n };\r\n\r\n return (\r\n <div \r\n className={`store-card ${isSelected ? 'active' : ''}`}\r\n onClick={onSelect}\r\n data-id={store.id}\r\n >\r\n <div className=\"store-card-header\">\r\n <div>\r\n <h4 className=\"store-name\">{store.name}</h4>\r\n <span className=\"store-city\">{store.city}</span>\r\n </div>\r\n <div className=\"store-price\">{store.price} تومان</div>\r\n </div>\r\n\r\n <p className=\"store-desc\">{store.desc}</p>\r\n\r\n <div className=\"store-details-wrapper\">\r\n <div className=\"store-details-content\">\r\n <div className=\"action-buttons\">\r\n {store.phone && (\r\n <a \r\n href={`tel:${store.phone}`} \r\n className=\"btn btn-call\" \r\n onClick={handleActionClick}\r\n >\r\n <span>تماس</span>\r\n </a>\r\n )}\r\n {store.whatsapp && (\r\n <a \r\n href={`https://wa.me/${store.whatsapp}`} \r\n target=\"_blank\" \r\n rel=\"noreferrer\"\r\n className=\"btn btn-wa\" \r\n onClick={handleActionClick}\r\n >\r\n <span>واتساپ</span>\r\n </a>\r\n )}\r\n <a \r\n href={googleMapUrl} \r\n target=\"_blank\" \r\n rel=\"noreferrer\"\r\n className=\"btn btn-route\" \r\n onClick={handleActionClick}\r\n >\r\n <span>مسیریابی</span>\r\n </a>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n );\r\n};"],"mappings":"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","names":["import_react","Supercluster","import_jsx_runtime","import_jsx_runtime"]}
|
package/dist/index.css
CHANGED
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
cursor: pointer;
|
|
43
43
|
transition: all 0.2s ease;
|
|
44
44
|
border-right: 4px solid transparent;
|
|
45
|
-
background: #
|
|
45
|
+
background: #ffffff;
|
|
46
46
|
}
|
|
47
47
|
.store-card:hover {
|
|
48
48
|
background: #f9fafb;
|
|
@@ -72,15 +72,15 @@
|
|
|
72
72
|
.store-price {
|
|
73
73
|
color: #059669;
|
|
74
74
|
font-weight: 800;
|
|
75
|
-
font-size:
|
|
75
|
+
font-size: 15px;
|
|
76
76
|
background: #ecfdf5;
|
|
77
77
|
padding: 2px 8px;
|
|
78
78
|
border-radius: 6px;
|
|
79
79
|
}
|
|
80
80
|
.store-desc {
|
|
81
|
-
font-size:
|
|
81
|
+
font-size: 12.5px;
|
|
82
82
|
color: #4b5563;
|
|
83
|
-
line-height: 1.
|
|
83
|
+
line-height: 1.6;
|
|
84
84
|
margin: 8px 0;
|
|
85
85
|
display: -webkit-box;
|
|
86
86
|
-webkit-line-clamp: 2;
|
|
@@ -103,7 +103,7 @@
|
|
|
103
103
|
.action-buttons {
|
|
104
104
|
display: flex;
|
|
105
105
|
gap: 8px;
|
|
106
|
-
padding-bottom:
|
|
106
|
+
padding-bottom: 6px;
|
|
107
107
|
}
|
|
108
108
|
.btn {
|
|
109
109
|
flex: 1;
|
|
@@ -111,7 +111,7 @@
|
|
|
111
111
|
border-radius: 8px;
|
|
112
112
|
text-align: center;
|
|
113
113
|
text-decoration: none;
|
|
114
|
-
font-size:
|
|
114
|
+
font-size: 13px;
|
|
115
115
|
font-weight: 700;
|
|
116
116
|
color: #fff;
|
|
117
117
|
transition: transform 0.1s, opacity 0.2s;
|
|
@@ -157,20 +157,19 @@
|
|
|
157
157
|
background: #ffffff;
|
|
158
158
|
border: 3px solid #2563eb;
|
|
159
159
|
border-radius: 50%;
|
|
160
|
-
width:
|
|
161
|
-
height:
|
|
160
|
+
width: 42px;
|
|
161
|
+
height: 42px;
|
|
162
162
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.25);
|
|
163
163
|
cursor: pointer;
|
|
164
|
+
transition: none;
|
|
164
165
|
}
|
|
165
166
|
.neshan-cluster-marker:hover {
|
|
166
|
-
|
|
167
|
-
box-shadow: 0 6px 25px rgba(0, 0, 0, 0.3);
|
|
167
|
+
transform: scale(1.05);
|
|
168
168
|
}
|
|
169
169
|
.neshan-cluster-count {
|
|
170
170
|
color: #2563eb;
|
|
171
171
|
font-weight: 900;
|
|
172
172
|
font-size: 16px;
|
|
173
|
-
pointer-events: none;
|
|
174
173
|
}
|
|
175
174
|
.neshan-marker-selected .neshan-marker-body {
|
|
176
175
|
animation: marker-bounce 0.8s infinite alternate cubic-bezier(0.45, 0.05, 0.55, 0.95);
|
|
@@ -186,46 +185,47 @@
|
|
|
186
185
|
}
|
|
187
186
|
.is-mobile .mobile-slider {
|
|
188
187
|
position: absolute;
|
|
189
|
-
bottom:
|
|
188
|
+
bottom: 0;
|
|
190
189
|
left: 0;
|
|
191
190
|
right: 0;
|
|
192
191
|
display: flex;
|
|
193
192
|
gap: 12px;
|
|
194
|
-
padding:
|
|
193
|
+
padding: 20px 20px 40px 20px;
|
|
195
194
|
overflow-x: auto;
|
|
196
195
|
scroll-snap-type: x mandatory;
|
|
197
|
-
pointer-events:
|
|
196
|
+
pointer-events: none;
|
|
198
197
|
z-index: 20;
|
|
199
198
|
align-items: flex-end;
|
|
199
|
+
scrollbar-width: none;
|
|
200
200
|
}
|
|
201
201
|
.is-mobile .mobile-slider::-webkit-scrollbar {
|
|
202
202
|
display: none;
|
|
203
203
|
}
|
|
204
204
|
.is-mobile .store-card {
|
|
205
|
-
min-width:
|
|
206
|
-
max-width:
|
|
205
|
+
min-width: 290px;
|
|
206
|
+
max-width: 80vw;
|
|
207
207
|
background: #ffffff;
|
|
208
|
-
border-radius:
|
|
209
|
-
box-shadow: 0
|
|
208
|
+
border-radius: 18px;
|
|
209
|
+
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.3);
|
|
210
210
|
pointer-events: auto;
|
|
211
211
|
scroll-snap-align: center;
|
|
212
212
|
border: none;
|
|
213
|
-
height: auto;
|
|
214
213
|
flex-shrink: 0;
|
|
214
|
+
max-height: 70vh;
|
|
215
|
+
overflow-y: auto;
|
|
215
216
|
}
|
|
216
217
|
.product-badge {
|
|
217
218
|
position: absolute;
|
|
218
|
-
top:
|
|
219
|
-
right:
|
|
219
|
+
top: 15px;
|
|
220
|
+
right: 15px;
|
|
220
221
|
background: rgba(255, 255, 255, 0.95);
|
|
221
222
|
backdrop-filter: blur(4px);
|
|
222
|
-
padding:
|
|
223
|
-
border-radius:
|
|
223
|
+
padding: 8px 16px;
|
|
224
|
+
border-radius: 10px;
|
|
224
225
|
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
|
|
225
226
|
font-weight: 800;
|
|
226
|
-
font-size:
|
|
227
|
+
font-size: 13px;
|
|
227
228
|
color: #1f2937;
|
|
228
229
|
z-index: 5;
|
|
229
|
-
border: 1px solid rgba(0, 0, 0, 0.05);
|
|
230
230
|
}
|
|
231
231
|
/*# sourceMappingURL=index.css.map */
|
package/dist/index.css.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/styles.css"],"sourcesContent":["@import \"@neshan-maps-platform/mapbox-gl/dist/NeshanMapboxGl.css\";\r\n\r\n.map-layout-root {\r\n display: flex;\r\n width: 100%;\r\n height: 100%;\r\n direction: rtl;\r\n font-family: inherit;\r\n overflow: hidden;\r\n}\r\n\r\n
|
|
1
|
+
{"version":3,"sources":["../src/styles.css"],"sourcesContent":["@import \"@neshan-maps-platform/mapbox-gl/dist/NeshanMapboxGl.css\";\r\n\r\n.map-layout-root {\r\n display: flex;\r\n width: 100%;\r\n height: 100%;\r\n direction: rtl;\r\n font-family: inherit;\r\n overflow: hidden;\r\n}\r\n\r\n/* Sidebar Desktop */\r\n.sidebar {\r\n width: 350px;\r\n background: #ffffff;\r\n border-left: 1px solid #e5e7eb;\r\n display: flex;\r\n flex-direction: column;\r\n z-index: 10;\r\n box-shadow: 2px 0 10px rgba(0, 0, 0, 0.05);\r\n}\r\n\r\n.sidebar-header {\r\n padding: 20px;\r\n font-weight: 800;\r\n font-size: 18px;\r\n color: #111827;\r\n border-bottom: 1px solid #f3f4f6;\r\n background: #fafafa;\r\n}\r\n\r\n.sidebar-list {\r\n flex: 1;\r\n overflow-y: auto;\r\n scrollbar-width: thin;\r\n}\r\n\r\n/* Map Section */\r\n.map-container {\r\n flex: 1;\r\n position: relative;\r\n background: #f3f4f6;\r\n}\r\n\r\n/* Store Card Style */\r\n.store-card {\r\n padding: 16px;\r\n border-bottom: 1px solid #f3f4f6;\r\n cursor: pointer;\r\n transition: all 0.2s ease;\r\n border-right: 4px solid transparent;\r\n background: #ffffff;\r\n}\r\n\r\n.store-card:hover {\r\n background: #f9fafb;\r\n}\r\n\r\n.store-card.active {\r\n background: #eff6ff;\r\n border-right-color: #2563eb;\r\n}\r\n\r\n.store-card-header {\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: flex-start;\r\n margin-bottom: 8px;\r\n}\r\n\r\n.store-name {\r\n margin: 0;\r\n font-size: 16px;\r\n font-weight: 700;\r\n color: #111827;\r\n}\r\n\r\n.store-city {\r\n display: block;\r\n font-size: 12px;\r\n color: #6b7280;\r\n margin-top: 2px;\r\n}\r\n\r\n.store-price {\r\n color: #059669;\r\n font-weight: 800;\r\n font-size: 15px;\r\n background: #ecfdf5;\r\n padding: 2px 8px;\r\n border-radius: 6px;\r\n}\r\n\r\n.store-desc {\r\n font-size: 12.5px;\r\n color: #4b5563;\r\n line-height: 1.6;\r\n margin: 8px 0;\r\n display: -webkit-box;\r\n -webkit-line-clamp: 2;\r\n -webkit-box-orient: vertical;\r\n overflow: hidden;\r\n}\r\n\r\n/* افکت آکاردئونی کارت */\r\n.store-details-wrapper {\r\n display: grid;\r\n grid-template-rows: 0fr;\r\n transition: grid-template-rows 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n overflow: hidden;\r\n}\r\n\r\n.active .store-details-wrapper {\r\n grid-template-rows: 1fr;\r\n margin-top: 14px;\r\n}\r\n\r\n.store-details-content {\r\n min-height: 0;\r\n}\r\n\r\n.action-buttons {\r\n display: flex;\r\n gap: 8px;\r\n padding-bottom: 6px;\r\n}\r\n\r\n.btn {\r\n flex: 1;\r\n padding: 10px 5px;\r\n border-radius: 8px;\r\n text-align: center;\r\n text-decoration: none;\r\n font-size: 13px;\r\n font-weight: 700;\r\n color: #fff;\r\n transition: transform 0.1s, opacity 0.2s;\r\n}\r\n\r\n.btn:active { transform: scale(0.95); }\r\n.btn-call { background: #2563eb; }\r\n.btn-wa { background: #10b981; }\r\n.btn-route { background: #7c3aed; }\r\n\r\n/* مارکرها */\r\n.neshan-marker-container {\r\n pointer-events: auto;\r\n}\r\n\r\n.neshan-marker-body {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n transform-origin: bottom center;\r\n}\r\n\r\n.neshan-marker-label {\r\n background: #2563eb;\r\n color: #ffffff !important;\r\n font-size: 12px;\r\n font-weight: 900;\r\n padding: 4px 12px;\r\n border-radius: 20px;\r\n margin-top: 6px;\r\n box-shadow: 0 4px 12px rgba(37, 99, 235, 0.4);\r\n border: 2px solid #ffffff;\r\n white-space: nowrap;\r\n pointer-events: none;\r\n}\r\n\r\n/* کلاسترها */\r\n.neshan-cluster-marker {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n background: #ffffff;\r\n border: 3px solid #2563eb;\r\n border-radius: 50%;\r\n width: 42px;\r\n height: 42px;\r\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.25);\r\n cursor: pointer;\r\n transition: none;\r\n}\r\n\r\n.neshan-cluster-marker:hover {\r\n transform: scale(1.05);\r\n}\r\n\r\n.neshan-cluster-count {\r\n color: #2563eb;\r\n font-weight: 900;\r\n font-size: 16px;\r\n}\r\n\r\n/* انیمیشن مارکر انتخاب شده */\r\n.neshan-marker-selected .neshan-marker-body {\r\n animation: marker-bounce 0.8s infinite alternate cubic-bezier(0.45, 0.05, 0.55, 0.95);\r\n z-index: 100;\r\n}\r\n\r\n@keyframes marker-bounce {\r\n from { transform: translateY(0) scale(1.1); }\r\n to { transform: translateY(-12px) scale(1.15); }\r\n}\r\n\r\n/* 📱 موبایل و اسلایدر (اصلاح شده) */\r\n.is-mobile .mobile-slider {\r\n position: absolute;\r\n bottom: 0; /* چسبیده به پایین برای مدیریت بهتر پدینگ */\r\n left: 0;\r\n right: 0;\r\n display: flex;\r\n gap: 12px;\r\n padding: 20px 20px 40px 20px; /* افزایش پدینگ پایین برای جلوگیری از مخفی شدن دکمهها */\r\n overflow-x: auto;\r\n scroll-snap-type: x mandatory;\r\n pointer-events: none;\r\n z-index: 20;\r\n align-items: flex-end; /* همتراز کردن از پایین */\r\n scrollbar-width: none;\r\n}\r\n\r\n.is-mobile .mobile-slider::-webkit-scrollbar { display: none; }\r\n\r\n.is-mobile .store-card {\r\n min-width: 290px;\r\n max-width: 80vw;\r\n background: #ffffff;\r\n border-radius: 18px;\r\n box-shadow: 0 8px 30px rgba(0, 0, 0, 0.3);\r\n pointer-events: auto;\r\n scroll-snap-align: center;\r\n border: none;\r\n flex-shrink: 0;\r\n /* جلوگیری از بزرگ شدن بیش از حد کارت در موبایلهای کوچک */\r\n max-height: 70vh; \r\n overflow-y: auto;\r\n}\r\n\r\n/* باج محصول */\r\n.product-badge {\r\n position: absolute;\r\n top: 15px;\r\n right: 15px;\r\n background: rgba(255, 255, 255, 0.95);\r\n backdrop-filter: blur(4px);\r\n padding: 8px 16px;\r\n border-radius: 10px;\r\n box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);\r\n font-weight: 800;\r\n font-size: 13px;\r\n color: #1f2937;\r\n z-index: 5;\r\n}"],"mappings":"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","names":[]}
|
package/dist/index.js
CHANGED
|
@@ -68,7 +68,6 @@ function createClusterElement(count) {
|
|
|
68
68
|
|
|
69
69
|
// src/components/Map.tsx
|
|
70
70
|
import Supercluster from "supercluster";
|
|
71
|
-
import "@neshan-maps-platform/mapbox-gl/dist/NeshanMapboxGl.css";
|
|
72
71
|
import { jsx } from "react/jsx-runtime";
|
|
73
72
|
var Map = ({
|
|
74
73
|
options,
|
|
@@ -206,6 +205,9 @@ var Map_default = Map;
|
|
|
206
205
|
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
207
206
|
var StoreCard = ({ store, isSelected, onSelect }) => {
|
|
208
207
|
const googleMapUrl = `https://www.google.com/maps/dir/?api=1&destination=${store.lat},${store.lng}`;
|
|
208
|
+
const handleActionClick = (e) => {
|
|
209
|
+
e.stopPropagation();
|
|
210
|
+
};
|
|
209
211
|
return /* @__PURE__ */ jsxs(
|
|
210
212
|
"div",
|
|
211
213
|
{
|
|
@@ -225,9 +227,37 @@ var StoreCard = ({ store, isSelected, onSelect }) => {
|
|
|
225
227
|
] }),
|
|
226
228
|
/* @__PURE__ */ jsx2("p", { className: "store-desc", children: store.desc }),
|
|
227
229
|
/* @__PURE__ */ jsx2("div", { className: "store-details-wrapper", children: /* @__PURE__ */ jsx2("div", { className: "store-details-content", children: /* @__PURE__ */ jsxs("div", { className: "action-buttons", children: [
|
|
228
|
-
store.phone && /* @__PURE__ */ jsx2(
|
|
229
|
-
|
|
230
|
-
|
|
230
|
+
store.phone && /* @__PURE__ */ jsx2(
|
|
231
|
+
"a",
|
|
232
|
+
{
|
|
233
|
+
href: `tel:${store.phone}`,
|
|
234
|
+
className: "btn btn-call",
|
|
235
|
+
onClick: handleActionClick,
|
|
236
|
+
children: /* @__PURE__ */ jsx2("span", { children: "\u062A\u0645\u0627\u0633" })
|
|
237
|
+
}
|
|
238
|
+
),
|
|
239
|
+
store.whatsapp && /* @__PURE__ */ jsx2(
|
|
240
|
+
"a",
|
|
241
|
+
{
|
|
242
|
+
href: `https://wa.me/${store.whatsapp}`,
|
|
243
|
+
target: "_blank",
|
|
244
|
+
rel: "noreferrer",
|
|
245
|
+
className: "btn btn-wa",
|
|
246
|
+
onClick: handleActionClick,
|
|
247
|
+
children: /* @__PURE__ */ jsx2("span", { children: "\u0648\u0627\u062A\u0633\u0627\u067E" })
|
|
248
|
+
}
|
|
249
|
+
),
|
|
250
|
+
/* @__PURE__ */ jsx2(
|
|
251
|
+
"a",
|
|
252
|
+
{
|
|
253
|
+
href: googleMapUrl,
|
|
254
|
+
target: "_blank",
|
|
255
|
+
rel: "noreferrer",
|
|
256
|
+
className: "btn btn-route",
|
|
257
|
+
onClick: handleActionClick,
|
|
258
|
+
children: /* @__PURE__ */ jsx2("span", { children: "\u0645\u0633\u06CC\u0631\u06CC\u0627\u0628\u06CC" })
|
|
259
|
+
}
|
|
260
|
+
)
|
|
231
261
|
] }) }) })
|
|
232
262
|
]
|
|
233
263
|
}
|
|
@@ -248,23 +278,22 @@ var InteractiveMap = (props) => {
|
|
|
248
278
|
}, []);
|
|
249
279
|
useEffect2(() => {
|
|
250
280
|
if (isMobile && props.selectedMarkerId && sliderRef.current) {
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
selectedCard
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
281
|
+
const timer = setTimeout(() => {
|
|
282
|
+
const selectedCard = sliderRef.current?.querySelector(`[data-id="${props.selectedMarkerId}"]`);
|
|
283
|
+
if (selectedCard) {
|
|
284
|
+
selectedCard.scrollIntoView({
|
|
285
|
+
behavior: "smooth",
|
|
286
|
+
inline: "center",
|
|
287
|
+
block: "nearest"
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
}, 100);
|
|
291
|
+
return () => clearTimeout(timer);
|
|
259
292
|
}
|
|
260
293
|
}, [props.selectedMarkerId, isMobile]);
|
|
261
294
|
const handleStoreSelect = useCallback2((store, index) => {
|
|
262
|
-
if (
|
|
263
|
-
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
if (props.onMarkerClick) {
|
|
267
|
-
props.onMarkerClick(store, index, mapInstance);
|
|
295
|
+
if (mapInstance) {
|
|
296
|
+
props.onMarkerClick?.(store, index, mapInstance);
|
|
268
297
|
}
|
|
269
298
|
}, [mapInstance, props]);
|
|
270
299
|
const safeMarkers = props.markers || [];
|
|
@@ -289,9 +318,7 @@ var InteractiveMap = (props) => {
|
|
|
289
318
|
onMapLoad: (map) => {
|
|
290
319
|
if (map) {
|
|
291
320
|
setMapInstance(map);
|
|
292
|
-
|
|
293
|
-
props.onMapLoad(map);
|
|
294
|
-
}
|
|
321
|
+
props.onMapLoad?.(map);
|
|
295
322
|
}
|
|
296
323
|
}
|
|
297
324
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/InteractiveMap.tsx","../src/components/Map.tsx","../src/utils/createCustomMarkerElement.ts","../src/utils/createClusterElement.ts","../src/components/StoreCard.tsx"],"sourcesContent":["\"use client\";\r\n\r\nimport React, { useState, useEffect, useRef, useCallback } from 'react';\r\nimport Map from './Map';\r\nimport { StoreCard } from './StoreCard';\r\nimport { Store, MapProps } from '../types';\r\n\r\nexport const InteractiveMap: React.FC<MapProps> = (props) => {\r\n const [isMobile, setIsMobile] = useState(false);\r\n const [mapInstance, setMapInstance] = useState<any>(null);\r\n const sliderRef = useRef<HTMLDivElement>(null);\r\n\r\n \r\n useEffect(() => {\r\n const checkMobile = () => setIsMobile(window.innerWidth < 1024);\r\n checkMobile();\r\n window.addEventListener('resize', checkMobile);\r\n return () => window.removeEventListener('resize', checkMobile);\r\n }, []);\r\n\r\n \r\n useEffect(() => {\r\n if (isMobile && props.selectedMarkerId && sliderRef.current) {\r\n const selectedCard = sliderRef.current.querySelector(`[data-id=\"${props.selectedMarkerId}\"]`);\r\n if (selectedCard) {\r\n selectedCard.scrollIntoView({\r\n behavior: 'smooth',\r\n inline: 'center',\r\n block: 'nearest'\r\n });\r\n }\r\n }\r\n }, [props.selectedMarkerId, isMobile]);\r\n\r\n \r\n const handleStoreSelect = useCallback((store: Store, index: number) => {\r\n \r\n if (!mapInstance) {\r\n console.warn(\"نقشه هنوز آماده نشده است.\");\r\n return;\r\n }\r\n \r\n \r\n if (props.onMarkerClick) {\r\n props.onMarkerClick(store, index, mapInstance);\r\n }\r\n }, [mapInstance, props]);\r\n\r\n const safeMarkers = props.markers || [];\r\n\r\n return (\r\n <div className={`map-layout-root ${isMobile ? 'is-mobile' : 'is-desktop'}`}>\r\n \r\n {/* سایدبار دسکتاپ */}\r\n {!isMobile && (\r\n <aside className=\"sidebar\">\r\n <header className=\"sidebar-header\">لیست فروشگاهها</header>\r\n <div className=\"sidebar-list\">\r\n {safeMarkers.map((store, idx) => (\r\n <StoreCard \r\n key={store.id} \r\n store={store} \r\n isSelected={props.selectedMarkerId === store.id}\r\n onSelect={() => handleStoreSelect(store, idx)}\r\n />\r\n ))}\r\n </div>\r\n </aside>\r\n )}\r\n\r\n {/* بخش نقشه */}\r\n <main className=\"map-container\">\r\n <Map \r\n {...props} \r\n onMapLoad={(map) => {\r\n \r\n if (map) {\r\n setMapInstance(map);\r\n if (props.onMapLoad) {\r\n props.onMapLoad(map);\r\n }\r\n }\r\n }} \r\n />\r\n \r\n {/* باج محصول */}\r\n {props.productName && (\r\n <div className=\"product-badge\">\r\n <span>{props.productName}</span>\r\n </div>\r\n )}\r\n\r\n {/* اسلایدر موبایل */}\r\n {isMobile && (\r\n <div className=\"mobile-slider\" ref={sliderRef}>\r\n {safeMarkers.map((store, idx) => (\r\n <StoreCard \r\n key={store.id} \r\n store={store} \r\n isSelected={props.selectedMarkerId === store.id}\r\n onSelect={() => handleStoreSelect(store, idx)}\r\n />\r\n ))}\r\n </div>\r\n )}\r\n </main>\r\n </div>\r\n );\r\n};","\"use client\";\r\n\r\nimport React, { useEffect, useRef, useState, useCallback } from 'react';\r\nimport type { MapboxMap, Store, MapProps } from '../types';\r\nimport { createCustomMarkerElement } from '../utils/createCustomMarkerElement';\r\nimport { createClusterElement } from '../utils/createClusterElement';\r\nimport Supercluster from 'supercluster';\r\n\r\nimport '@neshan-maps-platform/mapbox-gl/dist/NeshanMapboxGl.css';\r\n\r\nconst Map: React.FC<MapProps> = ({\r\n options,\r\n markers = [],\r\n markerLogoUrl = '',\r\n selectedMarkerId = null,\r\n onMarkerClick,\r\n onMapLoad,\r\n style = { width: '100%', height: '100%' },\r\n}) => {\r\n const mapContainerRef = useRef<HTMLDivElement>(null);\r\n const [mapInstance, setMapInstance] = useState<MapboxMap | null>(null);\r\n const [mapLib, setMapLib] = useState<any>(null);\r\n const markersRef = useRef<any[]>([]);\r\n const isRemoving = useRef(false); \r\n \r\n const clusterIndex = useRef(new Supercluster({\r\n radius: 60,\r\n maxZoom: 16\r\n }));\r\n\r\n \r\n useEffect(() => {\r\n let mounted = true;\r\n import('@neshan-maps-platform/mapbox-gl').then((mod) => {\r\n if (mounted) setMapLib(mod.default || mod);\r\n });\r\n return () => { mounted = false; };\r\n }, []);\r\n\r\n \r\n useEffect(() => {\r\n if (!mapLib || !mapContainerRef.current || mapInstance) return;\r\n\r\n const map = new mapLib.Map({\r\n container: mapContainerRef.current,\r\n ...options,\r\n });\r\n\r\n map.on('load', () => {\r\n if (!isRemoving.current) {\r\n setMapInstance(map);\r\n onMapLoad?.(map);\r\n }\r\n });\r\n\r\n return () => {\r\n if (map) {\r\n isRemoving.current = true;\r\n \r\n setTimeout(() => {\r\n try {\r\n map.remove();\r\n } catch (e) {\r\n console.warn(\"Mapbox remove error ignored:\", e);\r\n }\r\n }, 0);\r\n }\r\n };\r\n \r\n }, [mapLib]); \r\n\r\n \r\n useEffect(() => {\r\n if (!mapInstance || !selectedMarkerId || isRemoving.current) return;\r\n const selectedStore = markers.find(m => m.id === selectedMarkerId);\r\n if (selectedStore) {\r\n mapInstance.flyTo({\r\n center: [selectedStore.lng, selectedStore.lat],\r\n zoom: 16, \r\n essential: true,\r\n duration: 1200\r\n });\r\n }\r\n }, [selectedMarkerId, mapInstance, markers]);\r\n\r\n \r\n const updateMarkers = useCallback(() => {\r\n if (!mapInstance || !mapLib || isRemoving.current) return;\r\n\r\n try {\r\n const bounds = mapInstance.getBounds();\r\n const zoom = Math.floor(mapInstance.getZoom());\r\n\r\n const clusters = clusterIndex.current.getClusters(\r\n [bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth()],\r\n zoom\r\n );\r\n\r\n markersRef.current.forEach(m => m.remove());\r\n markersRef.current = [];\r\n\r\n clusters.forEach(cluster => {\r\n const [lng, lat] = cluster.geometry.coordinates;\r\n const { cluster: isCluster, point_count: count } = cluster.properties;\r\n\r\n let el: HTMLElement;\r\n\r\n if (isCluster) {\r\n el = createClusterElement(count);\r\n el.onclick = (e) => {\r\n e.stopPropagation();\r\n const expansionZoom = Math.min(\r\n clusterIndex.current.getClusterExpansionZoom(cluster.id as number),\r\n 18\r\n );\r\n mapInstance.easeTo({ center: [lng, lat], zoom: expansionZoom });\r\n };\r\n } else {\r\n const storeData = cluster.properties as Store;\r\n el = createCustomMarkerElement({\r\n marker: storeData,\r\n isSelected: storeData.id === selectedMarkerId,\r\n logoSrc: markerLogoUrl,\r\n });\r\n el.onclick = () => onMarkerClick?.(storeData, 0, mapInstance);\r\n }\r\n\r\n const newMarker = new mapLib.Marker({ element: el, anchor: isCluster ? 'center' : 'bottom' })\r\n .setLngLat([lng, lat])\r\n .addTo(mapInstance);\r\n \r\n markersRef.current.push(newMarker);\r\n });\r\n } catch (err) {\r\n console.error(\"Update markers failed:\", err);\r\n }\r\n }, [mapInstance, mapLib, selectedMarkerId, markerLogoUrl, onMarkerClick]);\r\n\r\n \r\n useEffect(() => {\r\n if (isRemoving.current) return;\r\n const points: any = markers.map(s => ({\r\n type: 'Feature',\r\n properties: { ...s },\r\n geometry: { type: 'Point', coordinates: [s.lng, s.lat] }\r\n }));\r\n clusterIndex.current.load(points);\r\n updateMarkers();\r\n }, [markers, updateMarkers]);\r\n\r\n \r\n useEffect(() => {\r\n if (!mapInstance || isRemoving.current) return;\r\n mapInstance.on('moveend', updateMarkers);\r\n return () => { mapInstance.off('moveend', updateMarkers); };\r\n }, [mapInstance, updateMarkers]);\r\n\r\n return (\r\n <div style={{ position: 'relative', overflow: 'hidden', ...style }}>\r\n <div \r\n ref={mapContainerRef} \r\n style={{ width: '100%', height: '100%', backfaceVisibility: 'hidden' }} \r\n />\r\n </div>\r\n );\r\n};\r\n\r\nexport default Map;","import type { MarkerData } from '../types';\r\n\r\ninterface CreateMarkerOptions {\r\n marker: MarkerData;\r\n isSelected: boolean;\r\n logoSrc?: string;\r\n showPrice?: boolean;\r\n}\r\n\r\nexport function createCustomMarkerElement({\r\n marker,\r\n isSelected,\r\n logoSrc = '',\r\n showPrice = true,\r\n}: CreateMarkerOptions): HTMLElement {\r\n \r\n const container = document.createElement('div');\r\n container.className = 'neshan-marker-container';\r\n\r\n \r\n const body = document.createElement('div');\r\n body.className = `neshan-marker-body ${isSelected ? 'neshan-marker-selected' : ''}`;\r\n \r\n \r\n Object.assign(body.style, {\r\n display: 'flex',\r\n flexDirection: 'column',\r\n alignItems: 'center',\r\n transition: 'transform 0.3s ease',\r\n transform: isSelected ? 'scale(1.2)' : 'scale(1)',\r\n });\r\n\r\n \r\n const iconBox = document.createElement('div');\r\n Object.assign(iconBox.style, {\r\n width: '36px',\r\n height: '36px',\r\n borderRadius: '50%',\r\n\r\n border: isSelected ? '3px solid #3b82f6' : '2px solid #ef4444',\r\n boxShadow: '0 4px 8px rgba(0,0,0,0.2)',\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n overflow: 'hidden'\r\n });\r\n\r\n if (logoSrc) {\r\n const img = document.createElement('img');\r\n img.src = logoSrc;\r\n img.style.width = '100%';\r\n img.style.height = '100%';\r\n img.style.objectFit = 'cover';\r\n iconBox.appendChild(img);\r\n } else {\r\n iconBox.style.backgroundColor = '#ef4444';\r\n }\r\n\r\n body.appendChild(iconBox);\r\n\r\n \r\n if (showPrice && (marker.price || marker.name)) {\r\n const label = document.createElement('div');\r\n label.className = 'neshan-marker-label'; // استفاده از کلاس CSS جدید\r\n label.textContent = marker.price ? `${marker.price} ت` : (marker.name ?? '');\r\n body.appendChild(label);\r\n}\r\n\r\n container.appendChild(body);\r\n return container;\r\n}","export function createClusterElement(count: number): HTMLElement {\r\n const container = document.createElement('div');\r\n container.className = 'neshan-cluster-marker';\r\n \r\n \r\n const countSpan = document.createElement('span');\r\n countSpan.className = 'neshan-cluster-count';\r\n countSpan.innerText = count.toString();\r\n \r\n container.appendChild(countSpan);\r\n return container;\r\n}","import React from 'react';\r\nimport { Store } from '../types';\r\n\r\ninterface StoreCardProps {\r\n store: Store;\r\n isSelected: boolean;\r\n onSelect: () => void;\r\n}\r\n\r\nexport const StoreCard: React.FC<StoreCardProps> = ({ store, isSelected, onSelect }) => {\r\n const googleMapUrl = `https://www.google.com/maps/dir/?api=1&destination=${store.lat},${store.lng}`;\r\n\r\n return (\r\n <div \r\n className={`store-card ${isSelected ? 'active' : ''}`}\r\n onClick={onSelect}\r\n data-id={store.id} // برای پیدا کردن کارت در اسکرول موبایل\r\n >\r\n <div className=\"store-card-header\">\r\n <div>\r\n <h4 className=\"store-name\">{store.name}</h4>\r\n <span className=\"store-city\">{store.city}</span>\r\n </div>\r\n <div className=\"store-price\">{store.price} تومان</div>\r\n </div>\r\n\r\n <p className=\"store-desc\">{store.desc}</p>\r\n\r\n {/* بخش آکاردئونی با CSS Animation */}\r\n <div className=\"store-details-wrapper\">\r\n <div className=\"store-details-content\">\r\n <div className=\"action-buttons\">\r\n {store.phone && (\r\n <a href={`tel:${store.phone}`} className=\"btn btn-call\" onClick={(e) => e.stopPropagation()}>\r\n <span>تماس</span>\r\n </a>\r\n )}\r\n {store.whatsapp && (\r\n <a href={`https://wa.me/${store.whatsapp}`} target=\"_blank\" className=\"btn btn-wa\" onClick={(e) => e.stopPropagation()}>\r\n <span>واتساپ</span>\r\n </a>\r\n )}\r\n <a href={googleMapUrl} target=\"_blank\" className=\"btn btn-route\" onClick={(e) => e.stopPropagation()}>\r\n <span>مسیریابی</span>\r\n </a>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n );\r\n};"],"mappings":"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","names":["useState","useEffect","useRef","useCallback","jsx","jsx","jsxs","useState","useRef","useEffect","useCallback"]}
|
|
1
|
+
{"version":3,"sources":["../src/components/InteractiveMap.tsx","../src/components/Map.tsx","../src/utils/createCustomMarkerElement.ts","../src/utils/createClusterElement.ts","../src/components/StoreCard.tsx"],"sourcesContent":["\"use client\";\r\n\r\nimport React, { useState, useEffect, useRef, useCallback } from 'react';\r\nimport Map from './Map';\r\nimport { StoreCard } from './StoreCard';\r\nimport { Store, MapProps } from '../types';\r\n\r\nexport const InteractiveMap: React.FC<MapProps> = (props) => {\r\n const [isMobile, setIsMobile] = useState(false);\r\n const [mapInstance, setMapInstance] = useState<any>(null);\r\n const sliderRef = useRef<HTMLDivElement>(null);\r\n\r\n useEffect(() => {\r\n const checkMobile = () => setIsMobile(window.innerWidth < 1024);\r\n checkMobile();\r\n window.addEventListener('resize', checkMobile);\r\n return () => window.removeEventListener('resize', checkMobile);\r\n }, []);\r\n\r\n // هماهنگی اسکرول اسلایدر موبایل با مارکر انتخاب شده\r\n useEffect(() => {\r\n if (isMobile && props.selectedMarkerId && sliderRef.current) {\r\n // استفاده از یک وقفه کوتاه برای اطمینان از پایان انیمیشن آکاردئون\r\n const timer = setTimeout(() => {\r\n const selectedCard = sliderRef.current?.querySelector(`[data-id=\"${props.selectedMarkerId}\"]`);\r\n if (selectedCard) {\r\n selectedCard.scrollIntoView({\r\n behavior: 'smooth',\r\n inline: 'center',\r\n block: 'nearest'\r\n });\r\n }\r\n }, 100);\r\n return () => clearTimeout(timer);\r\n }\r\n }, [props.selectedMarkerId, isMobile]);\r\n\r\n const handleStoreSelect = useCallback((store: Store, index: number) => {\r\n if (mapInstance) {\r\n props.onMarkerClick?.(store, index, mapInstance);\r\n }\r\n }, [mapInstance, props]);\r\n\r\n const safeMarkers = props.markers || [];\r\n\r\n return (\r\n <div className={`map-layout-root ${isMobile ? 'is-mobile' : 'is-desktop'}`}>\r\n \r\n {!isMobile && (\r\n <aside className=\"sidebar\">\r\n <header className=\"sidebar-header\">لیست فروشگاهها</header>\r\n <div className=\"sidebar-list\">\r\n {safeMarkers.map((store, idx) => (\r\n <StoreCard \r\n key={store.id} \r\n store={store} \r\n isSelected={props.selectedMarkerId === store.id}\r\n onSelect={() => handleStoreSelect(store, idx)}\r\n />\r\n ))}\r\n </div>\r\n </aside>\r\n )}\r\n\r\n <main className=\"map-container\">\r\n <Map \r\n {...props} \r\n onMapLoad={(map) => {\r\n if (map) {\r\n setMapInstance(map);\r\n props.onMapLoad?.(map);\r\n }\r\n }} \r\n />\r\n \r\n {props.productName && (\r\n <div className=\"product-badge\">\r\n <span>{props.productName}</span>\r\n </div>\r\n )}\r\n\r\n {isMobile && (\r\n <div className=\"mobile-slider\" ref={sliderRef}>\r\n {safeMarkers.map((store, idx) => (\r\n <StoreCard \r\n key={store.id} \r\n store={store} \r\n isSelected={props.selectedMarkerId === store.id}\r\n onSelect={() => handleStoreSelect(store, idx)}\r\n />\r\n ))}\r\n </div>\r\n )}\r\n </main>\r\n </div>\r\n );\r\n};","\"use client\";\r\n\r\nimport React, { useEffect, useRef, useState, useCallback } from 'react';\r\nimport type { MapboxMap, Store, MapProps } from '../types';\r\nimport { createCustomMarkerElement } from '../utils/createCustomMarkerElement';\r\nimport { createClusterElement } from '../utils/createClusterElement';\r\nimport Supercluster from 'supercluster';\r\n\r\n\r\nconst Map: React.FC<MapProps> = ({\r\n options,\r\n markers = [],\r\n markerLogoUrl = '',\r\n selectedMarkerId = null,\r\n onMarkerClick,\r\n onMapLoad,\r\n style = { width: '100%', height: '100%' },\r\n}) => {\r\n const mapContainerRef = useRef<HTMLDivElement>(null);\r\n const [mapInstance, setMapInstance] = useState<MapboxMap | null>(null);\r\n const [mapLib, setMapLib] = useState<any>(null);\r\n const markersRef = useRef<any[]>([]);\r\n const isRemoving = useRef(false); \r\n \r\n const clusterIndex = useRef(new Supercluster({\r\n radius: 60,\r\n maxZoom: 16\r\n }));\r\n\r\n \r\n useEffect(() => {\r\n let mounted = true;\r\n import('@neshan-maps-platform/mapbox-gl').then((mod) => {\r\n if (mounted) setMapLib(mod.default || mod);\r\n });\r\n return () => { mounted = false; };\r\n }, []);\r\n\r\n \r\n useEffect(() => {\r\n if (!mapLib || !mapContainerRef.current || mapInstance) return;\r\n\r\n const map = new mapLib.Map({\r\n container: mapContainerRef.current,\r\n ...options,\r\n });\r\n\r\n map.on('load', () => {\r\n if (!isRemoving.current) {\r\n setMapInstance(map);\r\n onMapLoad?.(map);\r\n }\r\n });\r\n\r\n return () => {\r\n if (map) {\r\n isRemoving.current = true;\r\n \r\n setTimeout(() => {\r\n try {\r\n map.remove();\r\n } catch (e) {\r\n console.warn(\"Mapbox remove error ignored:\", e);\r\n }\r\n }, 0);\r\n }\r\n };\r\n \r\n }, [mapLib]); \r\n\r\n \r\n useEffect(() => {\r\n if (!mapInstance || !selectedMarkerId || isRemoving.current) return;\r\n const selectedStore = markers.find(m => m.id === selectedMarkerId);\r\n if (selectedStore) {\r\n mapInstance.flyTo({\r\n center: [selectedStore.lng, selectedStore.lat],\r\n zoom: 16, \r\n essential: true,\r\n duration: 1200\r\n });\r\n }\r\n }, [selectedMarkerId, mapInstance, markers]);\r\n\r\n \r\n const updateMarkers = useCallback(() => {\r\n if (!mapInstance || !mapLib || isRemoving.current) return;\r\n\r\n try {\r\n const bounds = mapInstance.getBounds();\r\n const zoom = Math.floor(mapInstance.getZoom());\r\n\r\n const clusters = clusterIndex.current.getClusters(\r\n [bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth()],\r\n zoom\r\n );\r\n\r\n markersRef.current.forEach(m => m.remove());\r\n markersRef.current = [];\r\n\r\n clusters.forEach(cluster => {\r\n const [lng, lat] = cluster.geometry.coordinates;\r\n const { cluster: isCluster, point_count: count } = cluster.properties;\r\n\r\n let el: HTMLElement;\r\n\r\n if (isCluster) {\r\n el = createClusterElement(count);\r\n el.onclick = (e) => {\r\n e.stopPropagation();\r\n const expansionZoom = Math.min(\r\n clusterIndex.current.getClusterExpansionZoom(cluster.id as number),\r\n 18\r\n );\r\n mapInstance.easeTo({ center: [lng, lat], zoom: expansionZoom });\r\n };\r\n } else {\r\n const storeData = cluster.properties as Store;\r\n el = createCustomMarkerElement({\r\n marker: storeData,\r\n isSelected: storeData.id === selectedMarkerId,\r\n logoSrc: markerLogoUrl,\r\n });\r\n el.onclick = () => onMarkerClick?.(storeData, 0, mapInstance);\r\n }\r\n\r\n const newMarker = new mapLib.Marker({ element: el, anchor: isCluster ? 'center' : 'bottom' })\r\n .setLngLat([lng, lat])\r\n .addTo(mapInstance);\r\n \r\n markersRef.current.push(newMarker);\r\n });\r\n } catch (err) {\r\n console.error(\"Update markers failed:\", err);\r\n }\r\n }, [mapInstance, mapLib, selectedMarkerId, markerLogoUrl, onMarkerClick]);\r\n\r\n \r\n useEffect(() => {\r\n if (isRemoving.current) return;\r\n const points: any = markers.map(s => ({\r\n type: 'Feature',\r\n properties: { ...s },\r\n geometry: { type: 'Point', coordinates: [s.lng, s.lat] }\r\n }));\r\n clusterIndex.current.load(points);\r\n updateMarkers();\r\n }, [markers, updateMarkers]);\r\n\r\n \r\n useEffect(() => {\r\n if (!mapInstance || isRemoving.current) return;\r\n mapInstance.on('moveend', updateMarkers);\r\n return () => { mapInstance.off('moveend', updateMarkers); };\r\n }, [mapInstance, updateMarkers]);\r\n\r\n return (\r\n <div style={{ position: 'relative', overflow: 'hidden', ...style }}>\r\n <div \r\n ref={mapContainerRef} \r\n style={{ width: '100%', height: '100%', backfaceVisibility: 'hidden' }} \r\n />\r\n </div>\r\n );\r\n};\r\n\r\nexport default Map;","import type { MarkerData } from '../types';\r\n\r\ninterface CreateMarkerOptions {\r\n marker: MarkerData;\r\n isSelected: boolean;\r\n logoSrc?: string;\r\n showPrice?: boolean;\r\n}\r\n\r\nexport function createCustomMarkerElement({\r\n marker,\r\n isSelected,\r\n logoSrc = '',\r\n showPrice = true,\r\n}: CreateMarkerOptions): HTMLElement {\r\n \r\n const container = document.createElement('div');\r\n container.className = 'neshan-marker-container';\r\n\r\n \r\n const body = document.createElement('div');\r\n body.className = `neshan-marker-body ${isSelected ? 'neshan-marker-selected' : ''}`;\r\n \r\n \r\n Object.assign(body.style, {\r\n display: 'flex',\r\n flexDirection: 'column',\r\n alignItems: 'center',\r\n transition: 'transform 0.3s ease',\r\n transform: isSelected ? 'scale(1.2)' : 'scale(1)',\r\n });\r\n\r\n \r\n const iconBox = document.createElement('div');\r\n Object.assign(iconBox.style, {\r\n width: '36px',\r\n height: '36px',\r\n borderRadius: '50%',\r\n\r\n border: isSelected ? '3px solid #3b82f6' : '2px solid #ef4444',\r\n boxShadow: '0 4px 8px rgba(0,0,0,0.2)',\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n overflow: 'hidden'\r\n });\r\n\r\n if (logoSrc) {\r\n const img = document.createElement('img');\r\n img.src = logoSrc;\r\n img.style.width = '100%';\r\n img.style.height = '100%';\r\n img.style.objectFit = 'cover';\r\n iconBox.appendChild(img);\r\n } else {\r\n iconBox.style.backgroundColor = '#ef4444';\r\n }\r\n\r\n body.appendChild(iconBox);\r\n\r\n \r\n if (showPrice && (marker.price || marker.name)) {\r\n const label = document.createElement('div');\r\n label.className = 'neshan-marker-label'; // استفاده از کلاس CSS جدید\r\n label.textContent = marker.price ? `${marker.price} ت` : (marker.name ?? '');\r\n body.appendChild(label);\r\n}\r\n\r\n container.appendChild(body);\r\n return container;\r\n}","export function createClusterElement(count: number): HTMLElement {\r\n const container = document.createElement('div');\r\n container.className = 'neshan-cluster-marker';\r\n \r\n \r\n const countSpan = document.createElement('span');\r\n countSpan.className = 'neshan-cluster-count';\r\n countSpan.innerText = count.toString();\r\n \r\n container.appendChild(countSpan);\r\n return container;\r\n}","import React from 'react';\r\nimport { Store } from '../types';\r\n\r\ninterface StoreCardProps {\r\n store: Store;\r\n isSelected: boolean;\r\n onSelect: () => void;\r\n}\r\n\r\nexport const StoreCard: React.FC<StoreCardProps> = ({ store, isSelected, onSelect }) => {\r\n const googleMapUrl = `https://www.google.com/maps/dir/?api=1&destination=${store.lat},${store.lng}`;\r\n\r\n const handleActionClick = (e: React.MouseEvent) => {\r\n // جلوگیری از تغییر وضعیت انتخاب کارت هنگام کلیک روی دکمههای تماس/واتساپ\r\n e.stopPropagation();\r\n };\r\n\r\n return (\r\n <div \r\n className={`store-card ${isSelected ? 'active' : ''}`}\r\n onClick={onSelect}\r\n data-id={store.id}\r\n >\r\n <div className=\"store-card-header\">\r\n <div>\r\n <h4 className=\"store-name\">{store.name}</h4>\r\n <span className=\"store-city\">{store.city}</span>\r\n </div>\r\n <div className=\"store-price\">{store.price} تومان</div>\r\n </div>\r\n\r\n <p className=\"store-desc\">{store.desc}</p>\r\n\r\n <div className=\"store-details-wrapper\">\r\n <div className=\"store-details-content\">\r\n <div className=\"action-buttons\">\r\n {store.phone && (\r\n <a \r\n href={`tel:${store.phone}`} \r\n className=\"btn btn-call\" \r\n onClick={handleActionClick}\r\n >\r\n <span>تماس</span>\r\n </a>\r\n )}\r\n {store.whatsapp && (\r\n <a \r\n href={`https://wa.me/${store.whatsapp}`} \r\n target=\"_blank\" \r\n rel=\"noreferrer\"\r\n className=\"btn btn-wa\" \r\n onClick={handleActionClick}\r\n >\r\n <span>واتساپ</span>\r\n </a>\r\n )}\r\n <a \r\n href={googleMapUrl} \r\n target=\"_blank\" \r\n rel=\"noreferrer\"\r\n className=\"btn btn-route\" \r\n onClick={handleActionClick}\r\n >\r\n <span>مسیریابی</span>\r\n </a>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n );\r\n};"],"mappings":"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","names":["useState","useEffect","useRef","useCallback","jsx","jsx","jsxs","useState","useRef","useEffect","useCallback"]}
|