@navigoo/map-components 1.0.2 → 1.0.4
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/Dashboard.d.ts +1 -0
- package/dist/components/Dashboard.js +2 -8
- package/dist/components/DetourRouteSearch.d.ts +9 -0
- package/dist/components/DetourRouteSearch.js +5 -9
- package/dist/components/RouteSearch.d.ts +9 -0
- package/dist/components/RouteSearch.js +4 -8
- package/dist/components/SearchBar.d.ts +6 -0
- package/dist/components/SearchBar.js +2 -6
- package/dist/components/TransportOptions.d.ts +12 -1
- package/dist/components/TransportOptions.js +3 -7
- package/dist/components/TripType.d.ts +12 -1
- package/dist/components/TripType.js +3 -7
- package/package.json +1 -1
- package/src/components/Dashboard.tsx +52 -116
- package/src/components/DetourRouteSearch.tsx +42 -21
- package/src/components/MapView.tsx +100 -10
- package/src/components/RouteSearch.tsx +37 -16
- package/src/components/SearchBar.tsx +25 -10
- package/src/components/TransportOptions.tsx +31 -13
- package/src/components/TripType.tsx +31 -12
- package/src/components/styles.module.css +0 -207
@@ -1,15 +1,36 @@
|
|
1
|
-
import React, { useState, useEffect } from 'react';
|
1
|
+
import React, { useState, useEffect, ReactNode } from 'react';
|
2
2
|
import { Place, Route } from '../lib/type';
|
3
3
|
import { ApiClient } from '../lib/api';
|
4
|
-
import styles from './styles.module.css';
|
5
4
|
|
6
5
|
interface DetourRouteSearchProps {
|
7
6
|
apiClient: ApiClient;
|
8
7
|
setRoutes: (routes: Route[]) => void;
|
9
8
|
setSelectedRouteIndex: (index: number) => void;
|
9
|
+
className?: string;
|
10
|
+
searchGroupClassName?: string;
|
11
|
+
labelClassName?: string;
|
12
|
+
inputClassName?: string;
|
13
|
+
resultsClassName?: string;
|
14
|
+
resultItemClassName?: string;
|
15
|
+
errorClassName?: string;
|
16
|
+
buttonClassName?: string;
|
17
|
+
disabledButtonClassName?: string;
|
10
18
|
}
|
11
19
|
|
12
|
-
const DetourRouteSearch: React.FC<DetourRouteSearchProps> = ({
|
20
|
+
const DetourRouteSearch: React.FC<DetourRouteSearchProps> = ({
|
21
|
+
apiClient,
|
22
|
+
setRoutes,
|
23
|
+
setSelectedRouteIndex,
|
24
|
+
className,
|
25
|
+
searchGroupClassName,
|
26
|
+
labelClassName,
|
27
|
+
inputClassName,
|
28
|
+
resultsClassName,
|
29
|
+
resultItemClassName,
|
30
|
+
errorClassName,
|
31
|
+
buttonClassName,
|
32
|
+
disabledButtonClassName,
|
33
|
+
}) => {
|
13
34
|
const [startQuery, setStartQuery] = useState('');
|
14
35
|
const [detourQuery, setDetourQuery] = useState('');
|
15
36
|
const [endQuery, setEndQuery] = useState('');
|
@@ -120,9 +141,9 @@ const DetourRouteSearch: React.FC<DetourRouteSearchProps> = ({ apiClient, setRou
|
|
120
141
|
};
|
121
142
|
|
122
143
|
return (
|
123
|
-
<div className={
|
124
|
-
<div className={
|
125
|
-
<label htmlFor="start" className={
|
144
|
+
<div className={className}>
|
145
|
+
<div className={searchGroupClassName}>
|
146
|
+
<label htmlFor="start" className={labelClassName}>Départ</label>
|
126
147
|
<input
|
127
148
|
id="start"
|
128
149
|
type="text"
|
@@ -132,15 +153,15 @@ const DetourRouteSearch: React.FC<DetourRouteSearchProps> = ({ apiClient, setRou
|
|
132
153
|
handleSearch(e.target.value, setStartResults);
|
133
154
|
}}
|
134
155
|
placeholder="Point de départ"
|
135
|
-
className={
|
156
|
+
className={inputClassName}
|
136
157
|
/>
|
137
158
|
{startResults.length > 0 && (
|
138
|
-
<ul className={
|
159
|
+
<ul className={resultsClassName}>
|
139
160
|
{startResults.map((place) => (
|
140
161
|
<li
|
141
162
|
key={place.id}
|
142
163
|
onClick={() => handleSelectStart(place)}
|
143
|
-
className={
|
164
|
+
className={resultItemClassName}
|
144
165
|
>
|
145
166
|
{place.name}
|
146
167
|
</li>
|
@@ -148,8 +169,8 @@ const DetourRouteSearch: React.FC<DetourRouteSearchProps> = ({ apiClient, setRou
|
|
148
169
|
</ul>
|
149
170
|
)}
|
150
171
|
</div>
|
151
|
-
<div className={
|
152
|
-
<label htmlFor="detour" className={
|
172
|
+
<div className={searchGroupClassName}>
|
173
|
+
<label htmlFor="detour" className={labelClassName}>Détour</label>
|
153
174
|
<input
|
154
175
|
id="detour"
|
155
176
|
type="text"
|
@@ -159,15 +180,15 @@ const DetourRouteSearch: React.FC<DetourRouteSearchProps> = ({ apiClient, setRou
|
|
159
180
|
handleSearch(e.target.value, setDetourResults);
|
160
181
|
}}
|
161
182
|
placeholder="Point de détour"
|
162
|
-
className={
|
183
|
+
className={inputClassName}
|
163
184
|
/>
|
164
185
|
{detourResults.length > 0 && (
|
165
|
-
<ul className={
|
186
|
+
<ul className={resultsClassName}>
|
166
187
|
{detourResults.map((place) => (
|
167
188
|
<li
|
168
189
|
key={place.id}
|
169
190
|
onClick={() => handleSelectDetour(place)}
|
170
|
-
className={
|
191
|
+
className={resultItemClassName}
|
171
192
|
>
|
172
193
|
{place.name}
|
173
194
|
</li>
|
@@ -175,8 +196,8 @@ const DetourRouteSearch: React.FC<DetourRouteSearchProps> = ({ apiClient, setRou
|
|
175
196
|
</ul>
|
176
197
|
)}
|
177
198
|
</div>
|
178
|
-
<div className={
|
179
|
-
<label htmlFor="end" className={
|
199
|
+
<div className={searchGroupClassName}>
|
200
|
+
<label htmlFor="end" className={labelClassName}>Destination</label>
|
180
201
|
<input
|
181
202
|
id="end"
|
182
203
|
type="text"
|
@@ -186,15 +207,15 @@ const DetourRouteSearch: React.FC<DetourRouteSearchProps> = ({ apiClient, setRou
|
|
186
207
|
handleSearch(e.target.value, setEndResults);
|
187
208
|
}}
|
188
209
|
placeholder="Point d'arrivée"
|
189
|
-
className={
|
210
|
+
className={inputClassName}
|
190
211
|
/>
|
191
212
|
{endResults.length > 0 && (
|
192
|
-
<ul className={
|
213
|
+
<ul className={resultsClassName}>
|
193
214
|
{endResults.map((place) => (
|
194
215
|
<li
|
195
216
|
key={place.id}
|
196
217
|
onClick={() => handleSelectEnd(place)}
|
197
|
-
className={
|
218
|
+
className={resultItemClassName}
|
198
219
|
>
|
199
220
|
{place.name}
|
200
221
|
</li>
|
@@ -202,11 +223,11 @@ const DetourRouteSearch: React.FC<DetourRouteSearchProps> = ({ apiClient, setRou
|
|
202
223
|
</ul>
|
203
224
|
)}
|
204
225
|
</div>
|
205
|
-
{error && <div className={
|
226
|
+
{error && <div className={errorClassName}>{error}</div>}
|
206
227
|
<button
|
207
228
|
onClick={handleCalculateRoute}
|
208
229
|
disabled={loading || !selectedStart || !selectedDetour || !selectedEnd}
|
209
|
-
className={
|
230
|
+
className={loading || !selectedStart || !selectedDetour || !selectedEnd ? disabledButtonClassName : buttonClassName}
|
210
231
|
>
|
211
232
|
{loading ? 'Calcul...' : 'Calculer l\'itinéraire'}
|
212
233
|
</button>
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import React, { useEffect, useRef } from 'react';
|
1
|
+
import React, { useEffect, useRef, useState } from 'react';
|
2
2
|
import L from 'leaflet';
|
3
3
|
import 'leaflet/dist/leaflet.css';
|
4
4
|
import { parse } from 'wellknown';
|
@@ -28,6 +28,77 @@ const MapView: React.FC<MapViewProps> = ({
|
|
28
28
|
const routeLayerRef = useRef<L.LayerGroup | null>(null);
|
29
29
|
const clickMarkerRef = useRef<L.Marker | null>(null);
|
30
30
|
const routePolylinesRef = useRef<L.Polyline[]>([]);
|
31
|
+
const [ipLocation, setIpLocation] = useState<{ lat: number; lng: number } | null>(null);
|
32
|
+
|
33
|
+
// Fonction pour obtenir la localisation par IP
|
34
|
+
const getLocationByIP = async (): Promise<{ lat: number; lng: number } | null> => {
|
35
|
+
try {
|
36
|
+
const response = await fetch('https://ipapi.co/json/');
|
37
|
+
const data = await response.json();
|
38
|
+
|
39
|
+
if (data.latitude && data.longitude) {
|
40
|
+
return {
|
41
|
+
lat: data.latitude,
|
42
|
+
lng: data.longitude
|
43
|
+
};
|
44
|
+
}
|
45
|
+
return null;
|
46
|
+
} catch (error) {
|
47
|
+
console.error('Erreur de géolocalisation par IP:', error);
|
48
|
+
return null;
|
49
|
+
}
|
50
|
+
};
|
51
|
+
|
52
|
+
// Fonction pour obtenir la localisation par le navigateur
|
53
|
+
const getBrowserLocation = (): Promise<{ lat: number; lng: number }> => {
|
54
|
+
return new Promise((resolve, reject) => {
|
55
|
+
if (!navigator.geolocation) {
|
56
|
+
reject(new Error('Géolocalisation non supportée'));
|
57
|
+
return;
|
58
|
+
}
|
59
|
+
|
60
|
+
navigator.geolocation.getCurrentPosition(
|
61
|
+
(position) => {
|
62
|
+
resolve({
|
63
|
+
lat: position.coords.latitude,
|
64
|
+
lng: position.coords.longitude
|
65
|
+
});
|
66
|
+
},
|
67
|
+
(error) => {
|
68
|
+
reject(error);
|
69
|
+
},
|
70
|
+
{
|
71
|
+
enableHighAccuracy: true,
|
72
|
+
timeout: 10000,
|
73
|
+
maximumAge: 60000
|
74
|
+
}
|
75
|
+
);
|
76
|
+
});
|
77
|
+
};
|
78
|
+
|
79
|
+
// Effet pour la géolocalisation au démarrage
|
80
|
+
useEffect(() => {
|
81
|
+
const initializeLocation = async () => {
|
82
|
+
try {
|
83
|
+
// Essayer d'abord la géolocalisation du navigateur
|
84
|
+
const browserLocation = await getBrowserLocation();
|
85
|
+
setIpLocation(browserLocation);
|
86
|
+
} catch (browserError) {
|
87
|
+
console.log('Géolocalisation navigateur échouée, tentative par IP...', browserError);
|
88
|
+
|
89
|
+
// Fallback sur la géolocalisation par IP
|
90
|
+
const ipLocation = await getLocationByIP();
|
91
|
+
if (ipLocation) {
|
92
|
+
setIpLocation(ipLocation);
|
93
|
+
} else {
|
94
|
+
// Fallback final sur une position par défaut (centre du monde)
|
95
|
+
setIpLocation({ lat: 20, lng: 0 });
|
96
|
+
}
|
97
|
+
}
|
98
|
+
};
|
99
|
+
|
100
|
+
initializeLocation();
|
101
|
+
}, []);
|
31
102
|
|
32
103
|
const parseWKTLineString = (wkt: string): [number, number][] => {
|
33
104
|
try {
|
@@ -52,18 +123,20 @@ const MapView: React.FC<MapViewProps> = ({
|
|
52
123
|
|
53
124
|
useEffect(() => {
|
54
125
|
if (mapContainerRef.current && !mapRef.current) {
|
126
|
+
// Position par défaut centrée sur le monde, sera mise à jour par la géolocalisation
|
127
|
+
const defaultCenter = ipLocation || { lat: 20, lng: 0 };
|
128
|
+
|
55
129
|
mapRef.current = L.map(mapContainerRef.current, {
|
56
|
-
center: [
|
130
|
+
center: [defaultCenter.lat, defaultCenter.lng],
|
57
131
|
zoom: 12,
|
58
|
-
minZoom:
|
59
|
-
maxZoom:
|
60
|
-
|
61
|
-
maxBoundsViscosity: 1.0,
|
132
|
+
minZoom: 2, // Zoom minimal réduit pour voir le monde entier
|
133
|
+
maxZoom: 18, // Zoom maximal augmenté pour plus de détails
|
134
|
+
// Suppression des limites de la carte
|
62
135
|
});
|
63
136
|
|
64
137
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
65
138
|
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
|
66
|
-
maxZoom:
|
139
|
+
maxZoom: 18,
|
67
140
|
}).addTo(mapRef.current);
|
68
141
|
|
69
142
|
L.Icon.Default.mergeOptions({
|
@@ -91,13 +164,26 @@ const MapView: React.FC<MapViewProps> = ({
|
|
91
164
|
});
|
92
165
|
}
|
93
166
|
|
167
|
+
// Mettre à jour le centre de la carte quand la localisation par IP est disponible
|
168
|
+
if (mapRef.current && ipLocation && !userLocation && !searchedPlace && !routes) {
|
169
|
+
mapRef.current.setView([ipLocation.lat, ipLocation.lng], 12, { animate: true });
|
170
|
+
|
171
|
+
// Ajouter un marqueur pour la position détectée
|
172
|
+
if (!markerRef.current) {
|
173
|
+
markerRef.current = L.marker([ipLocation.lat, ipLocation.lng])
|
174
|
+
.addTo(mapRef.current)
|
175
|
+
.bindPopup(`<b>Votre position approximative</b><br>Détectée par IP<br>Lat: ${ipLocation.lat.toFixed(6)}<br>Lng: ${ipLocation.lng.toFixed(6)}`)
|
176
|
+
.openPopup();
|
177
|
+
}
|
178
|
+
}
|
179
|
+
|
94
180
|
return () => {
|
95
181
|
if (mapRef.current) {
|
96
182
|
mapRef.current.remove();
|
97
183
|
mapRef.current = null;
|
98
184
|
}
|
99
185
|
};
|
100
|
-
}, [apiClient]);
|
186
|
+
}, [apiClient, ipLocation]);
|
101
187
|
|
102
188
|
useEffect(() => {
|
103
189
|
if (!mapRef.current) return;
|
@@ -190,10 +276,14 @@ const MapView: React.FC<MapViewProps> = ({
|
|
190
276
|
centerOnPoint(searchedPlace.coordinates.lat, searchedPlace.coordinates.lng, searchedPlace.name);
|
191
277
|
} else if (userLocation) {
|
192
278
|
centerOnPoint(userLocation.latitude, userLocation.longitude, 'Votre position');
|
279
|
+
} else if (ipLocation) {
|
280
|
+
// Centrer sur la position IP si aucune autre position n'est disponible
|
281
|
+
centerOnPoint(ipLocation.lat, ipLocation.lng, 'Votre position approximative', 12);
|
193
282
|
} else {
|
194
|
-
|
283
|
+
// Position de fallback centrée sur le monde
|
284
|
+
mapRef.current!.setView([20, 0], 2, { animate: true });
|
195
285
|
}
|
196
|
-
}, [apiClient, userLocation, searchedPlace, routes, selectedRouteIndex, setSelectedRouteIndex]);
|
286
|
+
}, [apiClient, userLocation, searchedPlace, routes, selectedRouteIndex, setSelectedRouteIndex, ipLocation]);
|
197
287
|
|
198
288
|
return <div className="w-full h-screen" ref={mapContainerRef} />;
|
199
289
|
};
|
@@ -1,15 +1,36 @@
|
|
1
1
|
import React, { useState, useEffect } from 'react';
|
2
|
-
import { Place, Route
|
2
|
+
import { Place, Route } from '../lib/type';
|
3
3
|
import { ApiClient } from '../lib/api';
|
4
|
-
import styles from './styles.module.css';
|
5
4
|
|
6
5
|
interface RouteSearchProps {
|
7
6
|
apiClient: ApiClient;
|
8
7
|
setRoutes: (routes: Route[]) => void;
|
9
8
|
setSelectedRouteIndex: (index: number) => void;
|
9
|
+
className?: string;
|
10
|
+
searchGroupClassName?: string;
|
11
|
+
labelClassName?: string;
|
12
|
+
inputClassName?: string;
|
13
|
+
resultsClassName?: string;
|
14
|
+
resultItemClassName?: string;
|
15
|
+
errorClassName?: string;
|
16
|
+
buttonClassName?: string;
|
17
|
+
disabledButtonClassName?: string;
|
10
18
|
}
|
11
19
|
|
12
|
-
const RouteSearch: React.FC<RouteSearchProps> = ({
|
20
|
+
const RouteSearch: React.FC<RouteSearchProps> = ({
|
21
|
+
apiClient,
|
22
|
+
setRoutes,
|
23
|
+
setSelectedRouteIndex,
|
24
|
+
className,
|
25
|
+
searchGroupClassName,
|
26
|
+
labelClassName,
|
27
|
+
inputClassName,
|
28
|
+
resultsClassName,
|
29
|
+
resultItemClassName,
|
30
|
+
errorClassName,
|
31
|
+
buttonClassName,
|
32
|
+
disabledButtonClassName,
|
33
|
+
}) => {
|
13
34
|
const [startQuery, setStartQuery] = useState('');
|
14
35
|
const [endQuery, setEndQuery] = useState('');
|
15
36
|
const [startResults, setStartResults] = useState<Place[]>([]);
|
@@ -93,9 +114,9 @@ const RouteSearch: React.FC<RouteSearchProps> = ({ apiClient, setRoutes, setSele
|
|
93
114
|
};
|
94
115
|
|
95
116
|
return (
|
96
|
-
<div className={
|
97
|
-
<div className={
|
98
|
-
<label htmlFor="start" className={
|
117
|
+
<div className={className}>
|
118
|
+
<div className={searchGroupClassName}>
|
119
|
+
<label htmlFor="start" className={labelClassName}>Départ</label>
|
99
120
|
<input
|
100
121
|
id="start"
|
101
122
|
type="text"
|
@@ -105,15 +126,15 @@ const RouteSearch: React.FC<RouteSearchProps> = ({ apiClient, setRoutes, setSele
|
|
105
126
|
handleSearch(e.target.value, setStartResults);
|
106
127
|
}}
|
107
128
|
placeholder="Point de départ"
|
108
|
-
className={
|
129
|
+
className={inputClassName}
|
109
130
|
/>
|
110
131
|
{startResults.length > 0 && (
|
111
|
-
<ul className={
|
132
|
+
<ul className={resultsClassName}>
|
112
133
|
{startResults.map((place) => (
|
113
134
|
<li
|
114
135
|
key={place.id}
|
115
136
|
onClick={() => handleSelectStart(place)}
|
116
|
-
className={
|
137
|
+
className={resultItemClassName}
|
117
138
|
>
|
118
139
|
{place.name}
|
119
140
|
</li>
|
@@ -121,8 +142,8 @@ const RouteSearch: React.FC<RouteSearchProps> = ({ apiClient, setRoutes, setSele
|
|
121
142
|
</ul>
|
122
143
|
)}
|
123
144
|
</div>
|
124
|
-
<div className={
|
125
|
-
<label htmlFor="end" className={
|
145
|
+
<div className={searchGroupClassName}>
|
146
|
+
<label htmlFor="end" className={labelClassName}>Destination</label>
|
126
147
|
<input
|
127
148
|
id="end"
|
128
149
|
type="text"
|
@@ -132,15 +153,15 @@ const RouteSearch: React.FC<RouteSearchProps> = ({ apiClient, setRoutes, setSele
|
|
132
153
|
handleSearch(e.target.value, setEndResults);
|
133
154
|
}}
|
134
155
|
placeholder="Point d'arrivée"
|
135
|
-
className={
|
156
|
+
className={inputClassName}
|
136
157
|
/>
|
137
158
|
{endResults.length > 0 && (
|
138
|
-
<ul className={
|
159
|
+
<ul className={resultsClassName}>
|
139
160
|
{endResults.map((place) => (
|
140
161
|
<li
|
141
162
|
key={place.id}
|
142
163
|
onClick={() => handleSelectEnd(place)}
|
143
|
-
className={
|
164
|
+
className={resultItemClassName}
|
144
165
|
>
|
145
166
|
{place.name}
|
146
167
|
</li>
|
@@ -148,11 +169,11 @@ const RouteSearch: React.FC<RouteSearchProps> = ({ apiClient, setRoutes, setSele
|
|
148
169
|
</ul>
|
149
170
|
)}
|
150
171
|
</div>
|
151
|
-
{error && <div className={
|
172
|
+
{error && <div className={errorClassName}>{error}</div>}
|
152
173
|
<button
|
153
174
|
onClick={handleCalculateRoute}
|
154
175
|
disabled={loading || !selectedStart || !selectedEnd}
|
155
|
-
className={
|
176
|
+
className={loading || !selectedStart || !selectedEnd ? disabledButtonClassName : buttonClassName}
|
156
177
|
>
|
157
178
|
{loading ? 'Calcul...' : 'Calculer l\'itinéraire'}
|
158
179
|
</button>
|
@@ -1,15 +1,30 @@
|
|
1
1
|
import React, { useState } from 'react';
|
2
2
|
import { Place, GeolocationResult } from '../lib/type';
|
3
3
|
import { ApiClient } from '../lib/api';
|
4
|
-
import styles from './styles.module.css';
|
5
4
|
|
6
5
|
interface SearchBarProps {
|
7
6
|
apiClient: ApiClient;
|
8
7
|
setUserLocation: (location: GeolocationResult | null) => void;
|
9
8
|
setSearchedPlace: (place: Place | null) => void;
|
9
|
+
className?: string;
|
10
|
+
inputClassName?: string;
|
11
|
+
buttonClassName?: string;
|
12
|
+
errorClassName?: string;
|
13
|
+
resultsClassName?: string;
|
14
|
+
resultItemClassName?: string;
|
10
15
|
}
|
11
16
|
|
12
|
-
const SearchBar: React.FC<SearchBarProps> = ({
|
17
|
+
const SearchBar: React.FC<SearchBarProps> = ({
|
18
|
+
apiClient,
|
19
|
+
setUserLocation,
|
20
|
+
setSearchedPlace,
|
21
|
+
className,
|
22
|
+
inputClassName,
|
23
|
+
buttonClassName,
|
24
|
+
errorClassName,
|
25
|
+
resultsClassName,
|
26
|
+
resultItemClassName,
|
27
|
+
}) => {
|
13
28
|
const [query, setQuery] = useState('');
|
14
29
|
const [loading, setLoading] = useState(false);
|
15
30
|
const [results, setResults] = useState<Place[]>([]);
|
@@ -47,26 +62,26 @@ const SearchBar: React.FC<SearchBarProps> = ({ apiClient, setUserLocation, setSe
|
|
47
62
|
};
|
48
63
|
|
49
64
|
return (
|
50
|
-
<div className={
|
51
|
-
<div
|
52
|
-
<label htmlFor="search"
|
65
|
+
<div className={className}>
|
66
|
+
<div>
|
67
|
+
<label htmlFor="search">Nom du lieu</label>
|
53
68
|
<input
|
54
69
|
id="search"
|
55
70
|
type="text"
|
56
71
|
value={query}
|
57
72
|
onChange={(e) => setQuery(e.target.value)}
|
58
73
|
placeholder="Rechercher un lieu"
|
59
|
-
className={
|
74
|
+
className={inputClassName}
|
60
75
|
/>
|
61
76
|
</div>
|
62
|
-
{error && <div className={
|
77
|
+
{error && <div className={errorClassName}>{error}</div>}
|
63
78
|
{results.length > 0 && (
|
64
|
-
<ul className={
|
79
|
+
<ul className={resultsClassName}>
|
65
80
|
{results.map((place) => (
|
66
81
|
<li
|
67
82
|
key={place.id}
|
68
83
|
onClick={() => handleSelect(place)}
|
69
|
-
className={
|
84
|
+
className={resultItemClassName}
|
70
85
|
>
|
71
86
|
{place.name}
|
72
87
|
</li>
|
@@ -76,7 +91,7 @@ const SearchBar: React.FC<SearchBarProps> = ({ apiClient, setUserLocation, setSe
|
|
76
91
|
<button
|
77
92
|
onClick={handleSearch}
|
78
93
|
disabled={loading || !query}
|
79
|
-
className={
|
94
|
+
className={buttonClassName}
|
80
95
|
>
|
81
96
|
{loading ? 'Recherche...' : 'Rechercher'}
|
82
97
|
</button>
|
@@ -1,7 +1,28 @@
|
|
1
1
|
import React, { useState, useEffect } from 'react';
|
2
|
-
import styles from './styles.module.css';
|
3
2
|
|
4
|
-
|
3
|
+
interface TransportOptionsProps {
|
4
|
+
className?: string;
|
5
|
+
headerClassName?: string;
|
6
|
+
titleClassName?: string;
|
7
|
+
toggleButtonClassName?: string;
|
8
|
+
optionsClassName?: string;
|
9
|
+
optionClassName?: string;
|
10
|
+
selectedOptionClassName?: string;
|
11
|
+
iconClassName?: string;
|
12
|
+
nameClassName?: string;
|
13
|
+
}
|
14
|
+
|
15
|
+
const TransportOptions: React.FC<TransportOptionsProps> = ({
|
16
|
+
className,
|
17
|
+
headerClassName,
|
18
|
+
titleClassName,
|
19
|
+
toggleButtonClassName,
|
20
|
+
optionsClassName,
|
21
|
+
optionClassName,
|
22
|
+
selectedOptionClassName,
|
23
|
+
iconClassName,
|
24
|
+
nameClassName,
|
25
|
+
}) => {
|
5
26
|
const [showOptions, setShowOptions] = useState(false);
|
6
27
|
const [selectedTransport, setSelectedTransport] = useState('taxi');
|
7
28
|
|
@@ -16,37 +37,34 @@ const TransportOptions: React.FC = () => {
|
|
16
37
|
}, [selectedTransport]);
|
17
38
|
|
18
39
|
return (
|
19
|
-
<div className={
|
20
|
-
<div className={
|
21
|
-
<h3 className={
|
40
|
+
<div className={className}>
|
41
|
+
<div className={headerClassName}>
|
42
|
+
<h3 className={titleClassName}>Moyen de transport</h3>
|
22
43
|
<button
|
23
44
|
onClick={() => setShowOptions(!showOptions)}
|
24
|
-
className={
|
45
|
+
className={toggleButtonClassName}
|
25
46
|
>
|
26
47
|
{showOptions ? 'Réduire' : 'Options'}
|
27
48
|
</button>
|
28
49
|
</div>
|
29
50
|
{showOptions && (
|
30
|
-
<div className={
|
51
|
+
<div className={optionsClassName}>
|
31
52
|
{transportOptions.map((option) => (
|
32
53
|
<button
|
33
54
|
key={option.id}
|
34
55
|
onClick={() => setSelectedTransport(option.id)}
|
35
|
-
className={`${
|
36
|
-
selectedTransport === option.id ? styles.selected : ''
|
37
|
-
}`}
|
56
|
+
className={`${optionClassName} ${selectedTransport === option.id ? selectedOptionClassName : ''}`}
|
38
57
|
style={{
|
39
58
|
backgroundColor: selectedTransport === option.id ? option.color : undefined,
|
40
59
|
}}
|
41
60
|
>
|
42
|
-
<span className={
|
43
|
-
<span className={
|
61
|
+
<span className={iconClassName}>{option.icon}</span>
|
62
|
+
<span className={nameClassName}>{option.name}</span>
|
44
63
|
</button>
|
45
64
|
))}
|
46
65
|
</div>
|
47
66
|
)}
|
48
67
|
</div>
|
49
|
-
|
50
68
|
);
|
51
69
|
};
|
52
70
|
|
@@ -1,7 +1,28 @@
|
|
1
1
|
import React, { useState } from 'react';
|
2
|
-
import styles from './styles.module.css';
|
3
2
|
|
4
|
-
|
3
|
+
interface TripTypeProps {
|
4
|
+
className?: string;
|
5
|
+
headerClassName?: string;
|
6
|
+
titleClassName?: string;
|
7
|
+
toggleButtonClassName?: string;
|
8
|
+
optionsClassName?: string;
|
9
|
+
optionClassName?: string;
|
10
|
+
selectedOptionClassName?: string;
|
11
|
+
iconClassName?: string;
|
12
|
+
nameClassName?: string;
|
13
|
+
}
|
14
|
+
|
15
|
+
const TripType: React.FC<TripTypeProps> = ({
|
16
|
+
className,
|
17
|
+
headerClassName,
|
18
|
+
titleClassName,
|
19
|
+
toggleButtonClassName,
|
20
|
+
optionsClassName,
|
21
|
+
optionClassName,
|
22
|
+
selectedOptionClassName,
|
23
|
+
iconClassName,
|
24
|
+
nameClassName,
|
25
|
+
}) => {
|
5
26
|
const [showOptions, setShowOptions] = useState(false);
|
6
27
|
const [selectedTripType, setSelectedTripType] = useState('individuel');
|
7
28
|
|
@@ -11,31 +32,29 @@ const TripType: React.FC = () => {
|
|
11
32
|
];
|
12
33
|
|
13
34
|
return (
|
14
|
-
<div className={
|
15
|
-
<div className={
|
16
|
-
<h3 className={
|
35
|
+
<div className={className}>
|
36
|
+
<div className={headerClassName}>
|
37
|
+
<h3 className={titleClassName}>Type de trajet</h3>
|
17
38
|
<button
|
18
39
|
onClick={() => setShowOptions(!showOptions)}
|
19
|
-
className={
|
40
|
+
className={toggleButtonClassName}
|
20
41
|
>
|
21
42
|
{showOptions ? 'Réduire' : 'Options'}
|
22
43
|
</button>
|
23
44
|
</div>
|
24
45
|
{showOptions && (
|
25
|
-
<div className={
|
46
|
+
<div className={optionsClassName}>
|
26
47
|
{tripTypeOptions.map((option) => (
|
27
48
|
<button
|
28
49
|
key={option.id}
|
29
50
|
onClick={() => setSelectedTripType(option.id)}
|
30
|
-
className={`${
|
31
|
-
selectedTripType === option.id ? styles.selected : ''
|
32
|
-
}`}
|
51
|
+
className={`${optionClassName} ${selectedTripType === option.id ? selectedOptionClassName : ''}`}
|
33
52
|
style={{
|
34
53
|
backgroundColor: selectedTripType === option.id ? option.color : undefined,
|
35
54
|
}}
|
36
55
|
>
|
37
|
-
<span className={
|
38
|
-
<span className={
|
56
|
+
<span className={iconClassName}>{option.icon}</span>
|
57
|
+
<span className={nameClassName}>{option.name}</span>
|
39
58
|
</button>
|
40
59
|
))}
|
41
60
|
</div>
|