1
+ import { AnimatePresence , motion , useMotionValue , useSpring , useTransform } from 'framer-motion' ;
1
2
import 'leaflet/dist/leaflet.css' ;
2
3
import { useTheme } from 'next-themes' ;
3
4
import dynamic from 'next/dynamic' ;
4
5
import { useState } from 'react' ;
5
6
6
7
import { Airline , Flight } from 'services/content/flights' ;
7
8
8
- import { airportCoordinates , getAirlineColor } from 'utils/flights' ;
9
+ import { airportCoordinates , getAirlineColor , getTileUrl } from 'utils/flights' ;
9
10
import { classNames } from 'utils/styles' ;
10
11
12
+ import Typography from 'components/shared/typography' ;
13
+
11
14
const MapContainer = dynamic ( ( ) => import ( 'react-leaflet' ) . then ( ( mod ) => mod . MapContainer ) , { ssr : false } ) ;
12
15
const TileLayer = dynamic ( ( ) => import ( 'react-leaflet' ) . then ( ( mod ) => mod . TileLayer ) , { ssr : false } ) ;
13
16
const Polyline = dynamic ( ( ) => import ( 'react-leaflet' ) . then ( ( mod ) => mod . Polyline ) , { ssr : false } ) ;
@@ -57,15 +60,16 @@ interface MapProps {
57
60
58
61
function Map ( { flights, airports, isDarkMode } : MapProps ) {
59
62
const [ hoveredFlight , setHoveredFlight ] = useState < number | null > ( null ) ;
63
+ const x = useMotionValue ( 0 ) ;
64
+
65
+ const config = { damping : 5 , stiffness : 100 } ;
66
+ const rotate = useSpring ( useTransform ( x , [ - 100 , 100 ] , [ - 45 , 45 ] ) , config ) ;
67
+ const translateX = useSpring ( useTransform ( x , [ - 100 , 100 ] , [ - 47 , 47 ] ) , config ) ;
60
68
61
69
return (
62
70
< MapContainer center = { [ 20 , 0 ] } zoom = { 2 } className = "absolute inset-0 w-full h-full" >
63
71
< TileLayer
64
- url = {
65
- isDarkMode
66
- ? 'https://cartodb-basemaps-{s}.global.ssl.fastly.net/dark_all/{z}/{x}/{y}.png'
67
- : 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
68
- }
72
+ url = { getTileUrl ( isDarkMode ) }
69
73
attribution = '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
70
74
/>
71
75
{ flights . map ( ( flight , index ) => {
@@ -106,15 +110,39 @@ function Map({ flights, airports, isDarkMode }: MapProps) {
106
110
weight = { 1 }
107
111
fillOpacity = { 1 }
108
112
>
109
- < Tooltip direction = "top" offset = { [ 0 , - 5 ] } opacity = { 1 } permanent >
110
- < div
111
- className = { classNames ( 'px-2 py-1 rounded-full text-xs font-semibold' , {
112
- 'bg-gray-800 text-white' : isDarkMode ,
113
- 'bg-white text-gray-800 shadow-md border border-gray-200' : ! isDarkMode ,
114
- } ) }
115
- >
116
- { airport }
117
- </ div >
113
+ < Tooltip
114
+ direction = "auto"
115
+ offset = { [ 0 , - 5 ] }
116
+ opacity = { 1 }
117
+ className = "absolute inset-0 w-full h-full"
118
+ noArrow
119
+ sticky
120
+ >
121
+ < AnimatePresence mode = "popLayout" >
122
+ < motion . div
123
+ initial = { { opacity : 0 , scale : 0.6 , y : 20 } }
124
+ animate = { {
125
+ opacity : 1 ,
126
+ scale : 1 ,
127
+ transition : { damping : 10 , stiffness : 260 , type : 'spring' } ,
128
+ y : 0 ,
129
+ } }
130
+ exit = { { opacity : 0 , scale : 0.6 , y : 20 } }
131
+ style = { {
132
+ left : '50%' ,
133
+ rotate,
134
+ transform : 'translateX(50%)' ,
135
+ translateX : '-50%' ,
136
+ whiteSpace : 'nowrap' ,
137
+ x : translateX ,
138
+ } }
139
+ className = "absolute -top-10 z-50 flex flex-col items-center justify-center rounded-md bg-primary px-4 py-2 text-xs shadow-xl"
140
+ >
141
+ < Typography . small className = "relative z-30 text-base font-bold text-secondary" >
142
+ { airport }
143
+ </ Typography . small >
144
+ </ motion . div >
145
+ </ AnimatePresence >
118
146
</ Tooltip >
119
147
</ CircleMarker >
120
148
) ;
@@ -154,7 +182,7 @@ export default function FlightMap({ flights, airlines, airports }: FlightMapProp
154
182
setSelectedAirline = { setSelectedAirline }
155
183
isDarkMode = { isDarkMode }
156
184
/>
157
- < span
185
+ < Typography . small
158
186
className = { classNames ( 'text-sm' , {
159
187
'text-gray-300' : isDarkMode ,
160
188
'text-gray-600' : ! isDarkMode ,
@@ -163,11 +191,11 @@ export default function FlightMap({ flights, airlines, airports }: FlightMapProp
163
191
{ selectedAirline
164
192
? `${ filteredFlights . length } of ${ flights . length } flights`
165
193
: `${ flights . length } total flights` }
166
- </ span >
194
+ </ Typography . small >
167
195
</ div >
168
196
</ div >
169
197
</ div >
170
- < div className = "relative h-[60vh] mx-4 mt-4 rounded-lg overflow-hidden shadow-lg" >
198
+ < div className = "relative h-[60vh] w-full mt-4 rounded-lg overflow-hidden shadow-lg" >
171
199
< Map flights = { filteredFlights } airports = { airports } isDarkMode = { isDarkMode } />
172
200
</ div >
173
201
</ div >
0 commit comments