mehdi-akbari-map 1.3.2 → 1.3.3
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/index.cjs +47 -54
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +9 -7
- package/dist/index.css.map +1 -1
- package/dist/index.js +47 -54
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +47 -54
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +47 -54
- package/dist/react.js.map +1 -1
- package/dist/styles.css +70 -31
- package/package.json +1 -1
package/dist/react.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 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 timer = setTimeout(() => {\r\n const selectedCard = sliderRef.current?.querySelector(`[data-id=\"${props.selectedMarkerId}\"]`);\r\n if (selectedCard) {\r\n selectedCard.scrollIntoView({ behavior: 'smooth', inline: 'center', block: 'nearest' });\r\n }\r\n }, 150);\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 /* ✅ استایل inline برای اطمینان از پر کردن فضای مودال والد */\r\n <div \r\n className={`map-layout-root ${isMobile ? 'is-mobile' : 'is-desktop'}`}\r\n style={{ position: 'absolute', inset: 0, height: '100%', width: '100%' }}\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 shopLogoUrl={store.logoUrl}\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 <div className=\"product-badge-left-side\">\r\n {props.productLogoUrl && (\r\n <img src={props.productLogoUrl} alt=\"product\" className=\"product-badge-logo\" />\r\n )}\r\n <span className=\"product-badge-fixed-label\">فروشگاههای حضوری</span>\r\n </div>\r\n <div className=\"product-badge-separator\"></div>\r\n <span className=\"product-badge-text\">{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 shopLogoUrl={store.logoUrl}\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 { Store } from '../types';\r\n\r\ninterface CreateMarkerOptions {\r\n marker: Store;\r\n isSelected: boolean;\r\n logoSrc?: string;\r\n}\r\n\r\nexport function createCustomMarkerElement({\r\n marker,\r\n isSelected,\r\n logoSrc = '',\r\n}: CreateMarkerOptions): HTMLElement {\r\n const container = document.createElement('div');\r\n container.className = `neshan-marker-container ${isSelected ? 'neshan-marker-selected' : ''}`;\r\n\r\n const body = document.createElement('div');\r\n body.className = 'neshan-marker-body';\r\n\r\n const iconBox = document.createElement('div');\r\n Object.assign(iconBox.style, {\r\n width: '40px',\r\n height: '40px',\r\n borderRadius: '50%',\r\n backgroundColor: 'white',\r\n border: isSelected ? '3px solid #2563eb' : '',\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 = isSelected ? '#2563eb' : '#ef4444';\r\n }\r\n\r\n body.appendChild(iconBox);\r\n\r\n \r\n if (marker.name || marker.price) {\r\n const label = document.createElement('div');\r\n label.className = 'neshan-marker-label';\r\n \r\n const pricePart = marker.price ? ` (${marker.price} ت)` : '';\r\n label.textContent = `${marker.name}${pricePart}`;\r\n \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 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 shopLogoUrl?: string; // لوگوی کوچک کنار توضیحات\r\n}\r\n\r\nexport const StoreCard: React.FC<StoreCardProps> = ({ store, isSelected, onSelect, shopLogoUrl }) => {\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 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 {/* ردیف اول: نام، شهر و گزارش */}\r\n <div className=\"store-card-header\">\r\n <div className=\"store-info-main\">\r\n <h4 className=\"store-name\">{store.name}</h4>\r\n <div className=\"store-location-tag\">\r\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\"><path d=\"M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z\"></path><circle cx=\"12\" cy=\"10\" r=\"3\"></circle></svg>\r\n <span className=\"store-city\">{store.city}</span>\r\n </div>\r\n </div>\r\n <button className=\"report-btn\" onClick={handleActionClick}>\r\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#6b7280\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\"><path d=\"M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z\"></path><line x1=\"4\" y1=\"22\" x2=\"4\" y2=\"15\"></line></svg>\r\n <span>گزارش</span>\r\n </button>\r\n </div>\r\n\r\n {/* بخش عملکرد (اختیاری - اگر در دیتا بود) */}\r\n {store.performance && (\r\n <div className=\"store-performance\">\r\n عملکرد <span className=\"perf-value\">{store.performance}</span>\r\n </div>\r\n )}\r\n\r\n {/* بخش توضیحات همراه با لوگو */}\r\n <div className=\"store-desc-section\">\r\n {shopLogoUrl && (\r\n <img src={shopLogoUrl} alt=\"shop logo\" className=\"store-mini-logo\" />\r\n )}\r\n <p className=\"store-desc\">{store.desc}</p>\r\n </div>\r\n\r\n {/* ردیف آخر: قیمت و اطلاعات تماس */}\r\n <div className=\"store-card-footer\">\r\n <div className=\"price-container\">\r\n <span className=\"price-value\">{store.price}</span>\r\n <span className=\"price-unit\">تومان</span>\r\n </div>\r\n <div className=\"contact-trigger\">\r\n <span>اطلاعات تماس</span>\r\n <svg className={`arrow-icon ${isSelected ? 'open' : ''}`} width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\"><polyline points=\"18 15 12 9 6 15\"></polyline></svg>\r\n </div>\r\n </div>\r\n\r\n {/* بخش دکمههای عملیاتی (عمودی) */}\r\n <div className=\"store-details-wrapper\">\r\n <div className=\"store-details-content\">\r\n <div className=\"action-buttons vertical\">\r\n {store.phone && (\r\n <a href={`tel:${store.phone}`} className=\"btn btn-call full-width\" onClick={handleActionClick}>\r\n <div className=\"btn-content\">\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\"><path d=\"M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z\"></path></svg>\r\n <span>تماس: {store.phone}</span>\r\n </div>\r\n </a>\r\n )}\r\n {store.whatsapp && (\r\n <a href={`https://wa.me/${store.whatsapp}`} target=\"_blank\" rel=\"noreferrer\" className=\"btn btn-wa full-width\" onClick={handleActionClick}>\r\n <div className=\"btn-content\">\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\"><path d=\"M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z\"></path></svg>\r\n <span>ارسال پیام در واتساپ</span>\r\n </div>\r\n </a>\r\n )}\r\n <a href={googleMapUrl} target=\"_blank\" rel=\"noreferrer\" className=\"btn btn-route full-width\" onClick={handleActionClick}>\r\n <div className=\"btn-content\">\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\"><polygon points=\"3 11 22 2 13 21 11 13 3 11\"></polygon></svg>\r\n <span>مسیریابی روی نقشه</span>\r\n </div>\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 useEffect(() => {\r\n if (isMobile && props.selectedMarkerId && sliderRef.current) {\r\n const timer = setTimeout(() => {\r\n const selectedCard = sliderRef.current?.querySelector(`[data-id=\"${props.selectedMarkerId}\"]`);\r\n if (selectedCard) {\r\n selectedCard.scrollIntoView({ behavior: 'smooth', inline: 'center', block: 'nearest' });\r\n }\r\n }, 150);\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 /* ✅ استفاده از استایل ساده برای پر کردن فضای والد بدون شکستن لایهبندی */\r\n <div className={`map-layout-root ${isMobile ? 'is-mobile' : 'is-desktop'}`} style={{ width: '100%', height: '100%' }}>\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 shopLogoUrl={store.logoUrl}\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 <div className=\"product-badge-left-side\">\r\n {props.productLogoUrl && (\r\n <img src={props.productLogoUrl} alt=\"product\" className=\"product-badge-logo\" />\r\n )}\r\n <span className=\"product-badge-fixed-label\">فروشگاههای حضوری</span>\r\n </div>\r\n <div className=\"product-badge-separator\"></div>\r\n <span className=\"product-badge-text\">{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 shopLogoUrl={store.logoUrl}\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 { Store } from '../types';\r\n\r\ninterface CreateMarkerOptions {\r\n marker: Store;\r\n isSelected: boolean;\r\n logoSrc?: string;\r\n}\r\n\r\nexport function createCustomMarkerElement({\r\n marker,\r\n isSelected,\r\n logoSrc = '',\r\n}: CreateMarkerOptions): HTMLElement {\r\n const container = document.createElement('div');\r\n container.className = `neshan-marker-container ${isSelected ? 'neshan-marker-selected' : ''}`;\r\n\r\n const body = document.createElement('div');\r\n body.className = 'neshan-marker-body';\r\n\r\n const iconBox = document.createElement('div');\r\n Object.assign(iconBox.style, {\r\n width: '40px',\r\n height: '40px',\r\n borderRadius: '50%',\r\n backgroundColor: 'white',\r\n border: isSelected ? '3px solid #2563eb' : '',\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 = isSelected ? '#2563eb' : '#ef4444';\r\n }\r\n\r\n body.appendChild(iconBox);\r\n\r\n \r\n if (marker.name || marker.price) {\r\n const label = document.createElement('div');\r\n label.className = 'neshan-marker-label';\r\n \r\n const pricePart = marker.price ? ` (${marker.price} ت)` : '';\r\n label.textContent = `${marker.name}${pricePart}`;\r\n \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 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 shopLogoUrl?: string; // لوگوی کوچک کنار توضیحات\r\n}\r\n\r\nexport const StoreCard: React.FC<StoreCardProps> = ({ store, isSelected, onSelect, shopLogoUrl }) => {\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 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 {/* ردیف اول: نام، شهر و گزارش */}\r\n <div className=\"store-card-header\">\r\n <div className=\"store-info-main\">\r\n <h4 className=\"store-name\">{store.name}</h4>\r\n <div className=\"store-location-tag\">\r\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\"><path d=\"M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z\"></path><circle cx=\"12\" cy=\"10\" r=\"3\"></circle></svg>\r\n <span className=\"store-city\">{store.city}</span>\r\n </div>\r\n </div>\r\n <button className=\"report-btn\" onClick={handleActionClick}>\r\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#6b7280\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\"><path d=\"M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z\"></path><line x1=\"4\" y1=\"22\" x2=\"4\" y2=\"15\"></line></svg>\r\n <span>گزارش</span>\r\n </button>\r\n </div>\r\n\r\n {/* بخش عملکرد (اختیاری - اگر در دیتا بود) */}\r\n {store.performance && (\r\n <div className=\"store-performance\">\r\n عملکرد <span className=\"perf-value\">{store.performance}</span>\r\n </div>\r\n )}\r\n\r\n {/* بخش توضیحات همراه با لوگو */}\r\n <div className=\"store-desc-section\">\r\n {shopLogoUrl && (\r\n <img src={shopLogoUrl} alt=\"shop logo\" className=\"store-mini-logo\" />\r\n )}\r\n <p className=\"store-desc\">{store.desc}</p>\r\n </div>\r\n\r\n {/* ردیف آخر: قیمت و اطلاعات تماس */}\r\n <div className=\"store-card-footer\">\r\n <div className=\"price-container\">\r\n <span className=\"price-value\">{store.price}</span>\r\n <span className=\"price-unit\">تومان</span>\r\n </div>\r\n <div className=\"contact-trigger\">\r\n <span>اطلاعات تماس</span>\r\n <svg className={`arrow-icon ${isSelected ? 'open' : ''}`} width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\"><polyline points=\"18 15 12 9 6 15\"></polyline></svg>\r\n </div>\r\n </div>\r\n\r\n {/* بخش دکمههای عملیاتی (عمودی) */}\r\n <div className=\"store-details-wrapper\">\r\n <div className=\"store-details-content\">\r\n <div className=\"action-buttons vertical\">\r\n {store.phone && (\r\n <a href={`tel:${store.phone}`} className=\"btn btn-call full-width\" onClick={handleActionClick}>\r\n <div className=\"btn-content\">\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\"><path d=\"M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z\"></path></svg>\r\n <span>تماس: {store.phone}</span>\r\n </div>\r\n </a>\r\n )}\r\n {store.whatsapp && (\r\n <a href={`https://wa.me/${store.whatsapp}`} target=\"_blank\" rel=\"noreferrer\" className=\"btn btn-wa full-width\" onClick={handleActionClick}>\r\n <div className=\"btn-content\">\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\"><path d=\"M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z\"></path></svg>\r\n <span>ارسال پیام در واتساپ</span>\r\n </div>\r\n </a>\r\n )}\r\n <a href={googleMapUrl} target=\"_blank\" rel=\"noreferrer\" className=\"btn btn-route full-width\" onClick={handleActionClick}>\r\n <div className=\"btn-content\">\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\"><polygon points=\"3 11 22 2 13 21 11 13 3 11\"></polygon></svg>\r\n <span>مسیریابی روی نقشه</span>\r\n </div>\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"]}
|
package/dist/styles.css
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
@import "@neshan-maps-platform/mapbox-gl/dist/NeshanMapboxGl.css";
|
|
2
2
|
|
|
3
|
-
/* جلوگیری از بیرونزدگی و
|
|
3
|
+
/* ۱. جلوگیری از بیرونزدگی المانها و تنظیم پایه */
|
|
4
4
|
.map-layout-root,
|
|
5
5
|
.map-layout-root *,
|
|
6
6
|
.map-layout-root *::before,
|
|
@@ -11,27 +11,25 @@
|
|
|
11
11
|
.map-layout-root {
|
|
12
12
|
display: flex;
|
|
13
13
|
width: 100%;
|
|
14
|
-
height: 100%; /*
|
|
15
|
-
min-height: 100%;
|
|
14
|
+
height: 100%; /* فیت شدن با ارتفاع والد */
|
|
16
15
|
direction: rtl;
|
|
17
16
|
font-family: inherit;
|
|
18
|
-
overflow: hidden; /* جلوگیری از اسکرول خوردن کل کانتینر
|
|
19
|
-
position:
|
|
20
|
-
top: 0;
|
|
21
|
-
left: 0;
|
|
17
|
+
overflow: hidden; /* بسیار مهم: جلوگیری از اسکرول خوردن کل کانتینر پکیج */
|
|
18
|
+
position: relative;
|
|
22
19
|
}
|
|
23
20
|
|
|
24
|
-
/* سایدبار دسکتاپ */
|
|
21
|
+
/* سایدبار دسکتاپ - اصلاح شده برای مهار اسکرول */
|
|
25
22
|
.sidebar {
|
|
26
23
|
width: 350px;
|
|
27
|
-
height: 100%;
|
|
24
|
+
height: 100%; /* برابر با ارتفاع والد */
|
|
28
25
|
background: #ffffff;
|
|
29
26
|
border-left: 1px solid #e5e7eb;
|
|
30
27
|
display: flex;
|
|
31
28
|
flex-direction: column;
|
|
32
29
|
z-index: 1000;
|
|
33
30
|
box-shadow: 2px 0 10px rgba(0, 0, 0, 0.05);
|
|
34
|
-
|
|
31
|
+
flex-shrink: 0; /* جلوگیری از فشرده شدن سایدبار توسط نقشه */
|
|
32
|
+
min-height: 0; /* حیاتی برای محاسبات فلکسباکس */
|
|
35
33
|
}
|
|
36
34
|
|
|
37
35
|
.sidebar-header {
|
|
@@ -40,15 +38,16 @@
|
|
|
40
38
|
font-size: 18px;
|
|
41
39
|
color: #111827;
|
|
42
40
|
border-bottom: 1px solid #f3f4f6;
|
|
43
|
-
flex-shrink: 0;
|
|
41
|
+
flex-shrink: 0; /* هدر نباید اسکرول شود یا تغییر سایز دهد */
|
|
44
42
|
}
|
|
45
43
|
|
|
46
44
|
.sidebar-list {
|
|
47
|
-
flex: 1;
|
|
48
|
-
overflow-y: auto;
|
|
49
|
-
min-height: 0;
|
|
50
|
-
/* ✅ جلوگیری از اسکرول
|
|
45
|
+
flex: 1; /* اشغال تمام فضای باقیمانده سایدبار */
|
|
46
|
+
overflow-y: auto !important; /* فعال کردن اسکرول عمودی */
|
|
47
|
+
min-height: 0;
|
|
48
|
+
/* ✅ جلوگیری از انتقال اسکرول به صفحه اصلی سایت */
|
|
51
49
|
overscroll-behavior: contain;
|
|
50
|
+
/* بهبود حرکت اسکرول در مک و موبایل */
|
|
52
51
|
-webkit-overflow-scrolling: touch;
|
|
53
52
|
}
|
|
54
53
|
|
|
@@ -60,6 +59,8 @@
|
|
|
60
59
|
transition: background 0.2s ease;
|
|
61
60
|
background: #ffffff;
|
|
62
61
|
width: 100%;
|
|
62
|
+
display: flex;
|
|
63
|
+
flex-direction: column;
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
.store-card.active {
|
|
@@ -142,6 +143,7 @@
|
|
|
142
143
|
align-items: center;
|
|
143
144
|
border-top: 1px dashed #e5e7eb;
|
|
144
145
|
padding-top: 12px;
|
|
146
|
+
width: 100%;
|
|
145
147
|
}
|
|
146
148
|
|
|
147
149
|
.price-container {
|
|
@@ -176,9 +178,10 @@
|
|
|
176
178
|
transform: rotate(180deg);
|
|
177
179
|
}
|
|
178
180
|
|
|
179
|
-
.arrow-icon.open {
|
|
181
|
+
.arrow-icon.open {
|
|
182
|
+
transform: rotate(0deg);
|
|
183
|
+
}
|
|
180
184
|
|
|
181
|
-
/* دکمههای عمودی */
|
|
182
185
|
.action-buttons.vertical {
|
|
183
186
|
display: flex;
|
|
184
187
|
flex-direction: column;
|
|
@@ -209,13 +212,13 @@
|
|
|
209
212
|
.btn-wa { background: #10b981; color: white; }
|
|
210
213
|
.btn-route { background: #7c3aed; color: white; }
|
|
211
214
|
|
|
212
|
-
/* بخش نقشه */
|
|
213
215
|
.map-container {
|
|
214
216
|
flex: 1;
|
|
215
217
|
position: relative;
|
|
216
218
|
background: #f3f4f6;
|
|
217
|
-
height: 100%;
|
|
219
|
+
height: 100%; /* پر کردن کل فضای والد */
|
|
218
220
|
}
|
|
221
|
+
|
|
219
222
|
.store-details-wrapper {
|
|
220
223
|
display: grid;
|
|
221
224
|
grid-template-rows: 0fr;
|
|
@@ -226,7 +229,6 @@
|
|
|
226
229
|
.active .store-details-wrapper { grid-template-rows: 1fr; }
|
|
227
230
|
.store-details-content { min-height: 0; width: 100%; }
|
|
228
231
|
|
|
229
|
-
/* ✅ استایل مارکر: سفید، بردر نارنجی، متن مشکی */
|
|
230
232
|
.neshan-marker-container { pointer-events: auto; }
|
|
231
233
|
.neshan-marker-body {
|
|
232
234
|
display: flex;
|
|
@@ -235,21 +237,21 @@
|
|
|
235
237
|
transform-origin: bottom center;
|
|
236
238
|
}
|
|
237
239
|
|
|
240
|
+
/* ✅ استایل مارکر: پسزمینه سفید، بردر نارنجی، متن مشکی */
|
|
238
241
|
.neshan-marker-label {
|
|
239
242
|
background: #ffffff;
|
|
240
243
|
color: #111827 !important;
|
|
241
244
|
font-size: 11px;
|
|
242
245
|
font-weight: 800;
|
|
243
|
-
padding: 4px
|
|
246
|
+
padding: 4px 10px;
|
|
244
247
|
border-radius: 20px;
|
|
245
248
|
margin-top: 6px;
|
|
246
249
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
247
|
-
border: 2px solid #f97316; /* نارنجی */
|
|
250
|
+
border: 2px solid #f97316; /* بردر نارنجی */
|
|
248
251
|
white-space: nowrap;
|
|
249
252
|
pointer-events: none;
|
|
250
253
|
}
|
|
251
254
|
|
|
252
|
-
/* کلاستر */
|
|
253
255
|
.neshan-cluster-marker {
|
|
254
256
|
display: flex;
|
|
255
257
|
align-items: center;
|
|
@@ -269,7 +271,6 @@
|
|
|
269
271
|
font-size: 16px;
|
|
270
272
|
}
|
|
271
273
|
|
|
272
|
-
/* انیمیشن انتخاب */
|
|
273
274
|
.neshan-marker-selected .neshan-marker-body {
|
|
274
275
|
animation: marker-bounce 0.8s infinite alternate cubic-bezier(0.45, 0.05, 0.55, 0.95);
|
|
275
276
|
z-index: 100;
|
|
@@ -280,7 +281,6 @@
|
|
|
280
281
|
to { transform: translateY(-12px) scale(1.15); }
|
|
281
282
|
}
|
|
282
283
|
|
|
283
|
-
/* موبایل */
|
|
284
284
|
.is-mobile .mobile-slider {
|
|
285
285
|
position: absolute; bottom: 0; left: 0; right: 0;
|
|
286
286
|
display: flex; gap: 12px; padding: 20px 20px 40px 20px;
|
|
@@ -314,15 +314,54 @@
|
|
|
314
314
|
gap: 16px;
|
|
315
315
|
z-index: 40;
|
|
316
316
|
border: 1px solid rgba(0, 0, 0, 0.05);
|
|
317
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.product-badge-left-side {
|
|
321
|
+
display: flex;
|
|
322
|
+
flex-direction: column;
|
|
323
|
+
align-items: center;
|
|
324
|
+
gap: 4px;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
.product-badge-logo {
|
|
328
|
+
width: 65px;
|
|
329
|
+
height: 50px;
|
|
330
|
+
object-fit: contain;
|
|
331
|
+
border-radius: 12px;
|
|
332
|
+
padding: 1px;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.product-badge-fixed-label {
|
|
336
|
+
font-size: 9px;
|
|
337
|
+
font-weight: 800;
|
|
338
|
+
color: #64748b;
|
|
339
|
+
white-space: nowrap;
|
|
317
340
|
}
|
|
318
341
|
|
|
319
|
-
.product-badge-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
342
|
+
.product-badge-separator {
|
|
343
|
+
width: 1px;
|
|
344
|
+
height: 35px;
|
|
345
|
+
background: #e2e8f0;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
.product-badge-text {
|
|
349
|
+
font-weight: 850;
|
|
350
|
+
font-size: 12px;
|
|
351
|
+
color: #0f172a;
|
|
352
|
+
white-space: nowrap;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
.is-mobile .product-badge {
|
|
356
|
+
top: 16px;
|
|
357
|
+
bottom: auto;
|
|
358
|
+
right: 16px;
|
|
359
|
+
padding: 10px 14px;
|
|
360
|
+
border-radius: 16px;
|
|
361
|
+
gap: 12px;
|
|
362
|
+
max-width: 85vw;
|
|
363
|
+
}
|
|
324
364
|
|
|
325
|
-
.is-mobile .product-badge { top: 16px; bottom: auto; right: 16px; padding: 10px 14px; border-radius: 16px; gap: 12px; max-width: 85vw; }
|
|
326
365
|
.is-mobile .product-badge-logo { width: 32px; height: 32px; }
|
|
327
366
|
.is-mobile .product-badge-fixed-label { font-size: 8px; }
|
|
328
367
|
.is-mobile .product-badge-separator { height: 25px; }
|