+)
diff --git a/apps/www/components/Regions/RegionsList.tsx b/apps/www/components/Regions/RegionsList.tsx
index 3b3c031c8d9..5489614a574 100644
--- a/apps/www/components/Regions/RegionsList.tsx
+++ b/apps/www/components/Regions/RegionsList.tsx
@@ -5,11 +5,18 @@ import { Badge, cn } from 'ui'
import { GROUPED_REGIONS } from './Regions.constants'
interface Props {
- activeRegionCode?: string
+ selectedRegionCode: string | null
+ hoveredRegionCode?: string
onRegionHover: (code?: string) => void
+ onRegionSelect: (code: string) => void
}
-export const RegionsList = ({ activeRegionCode, onRegionHover }: Props) => (
+export const RegionsList = ({
+ selectedRegionCode,
+ hoveredRegionCode,
+ onRegionHover,
+ onRegionSelect,
+}: Props) => (
{GROUPED_REGIONS.map(({ group, regions }) => (
@@ -17,29 +24,50 @@ export const RegionsList = ({ activeRegionCode, onRegionHover }: Props) => (
{group}
- {regions.map((region) => (
- - onRegionHover(region.code)}
- onMouseLeave={() => onRegionHover(undefined)}
- className={cn(
- 'flex items-center gap-3 px-2 py-2.5 -mx-2 transition-colors',
- activeRegionCode === region.code && 'bg-surface-100'
- )}
- >
- {/* Flags are SVGs, which next/image can't serve while dangerouslyAllowSVG is off */}
-
- {region.displayName}
- {region.jurisdiction && {region.jurisdiction}}
-
- {region.code}
-
-
- ))}
+ {regions.map((region) => {
+ const isSelected = region.code === selectedRegionCode
+
+ return (
+ -
+
+
+ )
+ })}
))}
diff --git a/apps/www/components/Regions/RegionsMap.tsx b/apps/www/components/Regions/RegionsMap.tsx
index a7da67d0b7f..ba7e543a81a 100644
--- a/apps/www/components/Regions/RegionsMap.tsx
+++ b/apps/www/components/Regions/RegionsMap.tsx
@@ -1,85 +1,159 @@
'use client'
+import { geoEqualEarth } from 'd3-geo'
import { ComposableMap, Geographies, Geography, Marker, ZoomableGroup } from 'react-simple-maps'
import { cn } from 'ui'
import GeographyData from './MapData.json'
import { MAPPED_REGIONS } from './Regions.constants'
+import { RegionTooltip } from './RegionTooltip'
+
+/**
+ * The viewport is sized and framed so the land mass fills it edge to edge — the map is
+ * letterboxed inside its container otherwise. At scale 155 the world (Antarctica is not in
+ * MapData.json) projects to 828.3 x 361.5, so MAP_ZOOM shrinks it onto MAP_WIDTH and
+ * MAP_CENTER is the coordinate at the middle of that bounding box.
+ */
+const MAP_SCALE = 155
+const MAP_WIDTH = 800
+const MAP_HEIGHT = 349
+const MAP_ZOOM = 0.9659
+const MAP_CENTER: [number, number] = [1.06, 6.89]
+
+const projection = geoEqualEarth()
+ .scale(MAP_SCALE)
+ .translate([MAP_WIDTH / 2, MAP_HEIGHT / 2])
+const [centerX, centerY] = projection(MAP_CENTER) ?? [MAP_WIDTH / 2, MAP_HEIGHT / 2]
+
+/**
+ * Where each marker sits as a percentage of the container, mirroring what ZoomableGroup does to
+ * the SVG: `translate(width / 2 - centre * zoom) scale(zoom)`. The tooltip is positioned with
+ * these because anything rendered inside the SVG scales with the viewBox — text ends up at half
+ * size on a phone and twice that on a desktop.
+ */
+const REGION_POSITIONS = new Map(
+ MAPPED_REGIONS.map((region) => {
+ const [x, y] = projection(region.coordinates) ?? [centerX, centerY]
+
+ return [
+ region.code,
+ {
+ left: ((MAP_WIDTH / 2 + (x - centerX) * MAP_ZOOM) / MAP_WIDTH) * 100,
+ top: ((MAP_HEIGHT / 2 + (y - centerY) * MAP_ZOOM) / MAP_HEIGHT) * 100,
+ },
+ ]
+ })
+)
interface Props {
- activeRegionCode?: string
+ selectedRegionCode: string | null
+ hoveredRegionCode?: string
onRegionHover: (code?: string) => void
+ onRegionSelect: (code: string) => void
+ onRegionClear: () => void
}
-export const RegionsMap = ({ activeRegionCode, onRegionHover }: Props) => {
- const activeRegion = MAPPED_REGIONS.find((region) => region.code === activeRegionCode)
+export const RegionsMap = ({
+ selectedRegionCode,
+ hoveredRegionCode,
+ onRegionHover,
+ onRegionSelect,
+ onRegionClear,
+}: Props) => {
+ // Hovering previews a region, so it wins over the selection
+ const shownRegionCode = hoveredRegionCode ?? selectedRegionCode
+ const shownRegion = MAPPED_REGIONS.find((region) => region.code === shownRegionCode)
+ const shownPosition = shownRegion ? REGION_POSITIONS.get(shownRegion.code) : undefined
+ // Markers in the right half of the map get their tooltip on the left, so it can't run off
+ const isTooltipOnLeft = (shownPosition?.left ?? 0) > 50
return (
-
- false}
+ <>
+
-
- {({ geographies }) =>
- geographies.map((geo) => (
-
- ))
- }
-
-
- {MAPPED_REGIONS.map((region) => {
- const isActive = region.code === activeRegionCode
-
- return (
- onRegionHover(region.code)}
- onMouseLeave={() => onRegionHover(undefined)}
- >
- {isActive && }
-
-
- )
- })}
-
- {activeRegion && (
-
-
-
- {/* Flags are SVGs, which next/image can't serve while dangerouslyAllowSVG is off */}
-

false}
+ >
+
+ {({ geographies }) =>
+ geographies.map((geo) => (
+
-
- {activeRegion.displayName}
-
- {activeRegion.code}
-
-
-
-
-
- )}
-
-
+ ))
+ }
+
+
+ {MAPPED_REGIONS.map((region) => {
+ const isSelected = region.code === selectedRegionCode
+ const isHighlighted = isSelected || region.code === hoveredRegionCode
+
+ return (
+
onRegionHover(region.code)}
+ onMouseLeave={() => onRegionHover(undefined)}
+ onClick={() => onRegionSelect(region.code)}
+ >
+ {isSelected && }
+
+
+ )
+ })}
+
+
+
+ {shownRegion && shownPosition && (
+
+
+
+ )}
+ >
)
}
diff --git a/apps/www/package.json b/apps/www/package.json
index dc4473d2a09..2dbec8f9552 100644
--- a/apps/www/package.json
+++ b/apps/www/package.json
@@ -49,6 +49,7 @@
"common": "workspace:*",
"common-tags": "^1.8.2",
"config": "workspace:*",
+ "d3-geo": "^3.1.1",
"dayjs": "^1.11.12",
"dev-tools": "workspace:*",
"eslint-config-supabase": "workspace:*",
@@ -106,6 +107,7 @@
"@types/animejs": "^3.1.12",
"@types/classnames": "^2.3.1",
"@types/common-tags": "^1.8.4",
+ "@types/d3-geo": "^3.1.0",
"@types/mdast": "^4.0.4",
"@types/mdx-js__react": "^1.5.6",
"@types/parse-numeric-range": "^0.0.1",
diff --git a/apps/www/pages/regions.tsx b/apps/www/pages/regions.tsx
index 809ab325c19..58cf94c89fa 100644
--- a/apps/www/pages/regions.tsx
+++ b/apps/www/pages/regions.tsx
@@ -56,10 +56,6 @@ const RegionsPage = () => {
-
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 56a1694bddf..399de9f9508 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1735,6 +1735,9 @@ importers:
config:
specifier: workspace:*
version: link:../../packages/config
+ d3-geo:
+ specifier: ^3.1.1
+ version: 3.1.1
dayjs:
specifier: ^1.11.12
version: 1.11.13
@@ -1901,6 +1904,9 @@ importers:
'@types/common-tags':
specifier: ^1.8.4
version: 1.8.4
+ '@types/d3-geo':
+ specifier: ^3.1.0
+ version: 3.1.0
'@types/mdast':
specifier: ^4.0.4
version: 4.0.4