diff --git a/apps/web/components/map/listing-map.tsx b/apps/web/components/map/listing-map.tsx index 2076299..b3067d3 100644 --- a/apps/web/components/map/listing-map.tsx +++ b/apps/web/components/map/listing-map.tsx @@ -94,6 +94,18 @@ function ListingMapInner({ listings, onMarkerClick, selectedListingId, className map.addControl(new mapboxgl.NavigationControl(), 'top-right'); map.addControl(new mapboxgl.AttributionControl({ compact: true }), 'bottom-right'); + // Patch ARIA labels onto Mapbox's auto-generated navigation buttons (Vietnamese) + map.once('load', () => { + const container = mapContainerRef.current; + if (!container) return; + const zoomIn = container.querySelector('.mapboxgl-ctrl-zoom-in') as HTMLButtonElement | null; + const zoomOut = container.querySelector('.mapboxgl-ctrl-zoom-out') as HTMLButtonElement | null; + const compass = container.querySelector('.mapboxgl-ctrl-compass') as HTMLButtonElement | null; + if (zoomIn) zoomIn.setAttribute('aria-label', 'Phóng to'); + if (zoomOut) zoomOut.setAttribute('aria-label', 'Thu nhỏ'); + if (compass) compass.setAttribute('aria-label', 'Đặt lại hướng bắc'); + }); + mapRef.current = map; return () => { @@ -128,10 +140,21 @@ function ListingMapInner({ listings, onMarkerClick, selectedListingId, className el.className = 'mapbox-price-marker'; const isSelected = selectedListingId === marker.listing.id; const span = document.createElement('span'); - if (isSelected) span.className = 'selected'; + if (isSelected) { + span.className = 'selected'; + el.setAttribute('aria-pressed', 'true'); + } else { + el.setAttribute('aria-pressed', 'false'); + } span.textContent = formatPrice(marker.listing.priceVND); el.appendChild(span); el.style.cssText = 'border:none;cursor:pointer;background:none;padding:0;'; + const { property } = marker.listing; + const address = [property.district, property.city].filter(Boolean).join(', '); + el.setAttribute( + 'aria-label', + `${formatPrice(marker.listing.priceVND)} VND – ${property.title}${address ? `, ${address}` : ''}`, + ); el.addEventListener('click', (e) => { e.stopPropagation(); @@ -157,6 +180,8 @@ function ListingMapInner({ listings, onMarkerClick, selectedListingId, className function buildPopupContent(listing: ListingDetail): HTMLDivElement { const container = document.createElement('div'); + container.setAttribute('role', 'dialog'); + container.setAttribute('aria-label', `Chi tiết: ${listing.property.title}`); container.style.cssText = 'font-family:system-ui,sans-serif;background:hsl(var(--card));color:hsl(var(--card-foreground));padding:8px;border-radius:6px;'; if ((listing.property.media?.length ?? 0) > 0) { @@ -188,7 +213,7 @@ function ListingMapInner({ listings, onMarkerClick, selectedListingId, className const areaTag = document.createElement('span'); areaTag.style.cssText = tagStyle; - areaTag.textContent = `${listing.property.areaM2} m\u00B2`; + areaTag.textContent = `${listing.property.areaM2} m²`; details.appendChild(areaTag); if (listing.property.bedrooms != null) { @@ -208,7 +233,7 @@ function ListingMapInner({ listings, onMarkerClick, selectedListingId, className const link = document.createElement('a'); link.href = `/listings/${listing.id}`; link.style.cssText = 'display:block;text-align:center;font-size:12px;font-weight:500;color:hsl(var(--primary));text-decoration:none;'; - link.textContent = 'Xem chi ti\u1EBFt \u2192'; + link.textContent = 'Xem chi tiết →'; container.appendChild(link); return container; @@ -228,7 +253,11 @@ function ListingMapInner({ listings, onMarkerClick, selectedListingId, className const hasToken = typeof process !== 'undefined' && process.env['NEXT_PUBLIC_MAPBOX_TOKEN']; return ( -
Không có bất động sản để hiển thị trên bản đồ