@tpzdsp/next-toolkit 2.0.0 → 2.0.1
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/package.json +1 -1
- package/src/map/geocoder/Geocoder.tsx +16 -82
package/package.json
CHANGED
|
@@ -3,73 +3,13 @@
|
|
|
3
3
|
import { useCallback, useEffect, useId, useRef, useState } from 'react';
|
|
4
4
|
|
|
5
5
|
import type Map from 'ol/Map';
|
|
6
|
+
import { ImSpinner2 } from 'react-icons/im';
|
|
7
|
+
import { IoClose, IoSearch } from 'react-icons/io5';
|
|
6
8
|
|
|
7
9
|
import { groupResults } from './groupResults';
|
|
8
10
|
import type { GeocoderResult } from './types';
|
|
9
11
|
import { useDebounce } from '../../hooks/useDebounce';
|
|
10
12
|
|
|
11
|
-
// SVG Icons matching Mapbox GL Geocoder style
|
|
12
|
-
const SearchIcon = () => (
|
|
13
|
-
<svg
|
|
14
|
-
className="absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none"
|
|
15
|
-
width="20"
|
|
16
|
-
height="20"
|
|
17
|
-
viewBox="0 0 18 18"
|
|
18
|
-
fill="none"
|
|
19
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
20
|
-
>
|
|
21
|
-
<path
|
|
22
|
-
d="M7.5 13.5C10.8137 13.5 13.5 10.8137 13.5 7.5C13.5 4.18629 10.8137 1.5 7.5 1.5C4.18629 1.5 1.5 4.18629 1.5 7.5C1.5 10.8137 4.18629 13.5 7.5 13.5Z"
|
|
23
|
-
stroke="#757575"
|
|
24
|
-
strokeWidth="1.5"
|
|
25
|
-
strokeLinecap="round"
|
|
26
|
-
strokeLinejoin="round"
|
|
27
|
-
/>
|
|
28
|
-
|
|
29
|
-
<path
|
|
30
|
-
d="M16.5 16.5L11.625 11.625"
|
|
31
|
-
stroke="#757575"
|
|
32
|
-
strokeWidth="1.5"
|
|
33
|
-
strokeLinecap="round"
|
|
34
|
-
strokeLinejoin="round"
|
|
35
|
-
/>
|
|
36
|
-
</svg>
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
const ClearIcon = () => (
|
|
40
|
-
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
41
|
-
<path
|
|
42
|
-
d="M15 5L5 15M5 5L15 15"
|
|
43
|
-
stroke="#757575"
|
|
44
|
-
strokeWidth="2"
|
|
45
|
-
strokeLinecap="round"
|
|
46
|
-
strokeLinejoin="round"
|
|
47
|
-
/>
|
|
48
|
-
</svg>
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
const LoadingIcon = () => (
|
|
52
|
-
<svg
|
|
53
|
-
className="animate-spin"
|
|
54
|
-
width="26"
|
|
55
|
-
height="26"
|
|
56
|
-
viewBox="0 0 26 26"
|
|
57
|
-
fill="none"
|
|
58
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
59
|
-
>
|
|
60
|
-
<circle
|
|
61
|
-
cx="13"
|
|
62
|
-
cy="13"
|
|
63
|
-
r="10"
|
|
64
|
-
stroke="#757575"
|
|
65
|
-
strokeWidth="3"
|
|
66
|
-
strokeLinecap="round"
|
|
67
|
-
strokeDasharray="60"
|
|
68
|
-
strokeDashoffset="40"
|
|
69
|
-
/>
|
|
70
|
-
</svg>
|
|
71
|
-
);
|
|
72
|
-
|
|
73
13
|
// Typeahead defaults
|
|
74
14
|
const DEFAULT_TYPEAHEAD_DELAY_MS = 500;
|
|
75
15
|
|
|
@@ -217,19 +157,11 @@ export const Geocoder = ({
|
|
|
217
157
|
if (shouldZoomOut) {
|
|
218
158
|
// Step 1: Zoom out, then combined pan + zoom in (like ol-geocoder)
|
|
219
159
|
view.animate({ zoom: flyZoom, duration: zoomOutDuration }, () => {
|
|
220
|
-
view.animate({
|
|
221
|
-
center: result.center,
|
|
222
|
-
zoom: finalZoom,
|
|
223
|
-
duration: flyToDuration,
|
|
224
|
-
});
|
|
160
|
+
view.animate({ center: result.center, zoom: finalZoom, duration: flyToDuration });
|
|
225
161
|
});
|
|
226
162
|
} else {
|
|
227
163
|
// Combined pan + zoom in one smooth animation
|
|
228
|
-
view.animate({
|
|
229
|
-
center: result.center,
|
|
230
|
-
zoom: finalZoom,
|
|
231
|
-
duration: flyToDuration,
|
|
232
|
-
});
|
|
164
|
+
view.animate({ center: result.center, zoom: finalZoom, duration: flyToDuration });
|
|
233
165
|
}
|
|
234
166
|
}
|
|
235
167
|
|
|
@@ -278,12 +210,14 @@ export const Geocoder = ({
|
|
|
278
210
|
Search for a place
|
|
279
211
|
</label>
|
|
280
212
|
|
|
281
|
-
{/* Mapbox-style geocoder container */}
|
|
282
213
|
<div
|
|
283
214
|
className="relative bg-white rounded shadow-lg transition-all duration-200
|
|
284
215
|
focus-within:ring-2 focus-within:ring-focus"
|
|
285
216
|
>
|
|
286
|
-
<
|
|
217
|
+
<IoSearch
|
|
218
|
+
size={20}
|
|
219
|
+
className="absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none text-gray-400"
|
|
220
|
+
/>
|
|
287
221
|
|
|
288
222
|
<input
|
|
289
223
|
ref={inputRef}
|
|
@@ -336,13 +270,12 @@ export const Geocoder = ({
|
|
|
336
270
|
placeholder:text-gray-500 focus:outline-none focus:ring-0"
|
|
337
271
|
/>
|
|
338
272
|
|
|
339
|
-
{/* Clear or Loading button */}
|
|
340
273
|
<div className="absolute right-2 top-1/2 -translate-y-1/2">
|
|
341
274
|
{(() => {
|
|
342
275
|
if (isSearching) {
|
|
343
276
|
return (
|
|
344
277
|
<div className="p-1">
|
|
345
|
-
<
|
|
278
|
+
<ImSpinner2 size={22} className="animate-spin text-gray-500" />
|
|
346
279
|
</div>
|
|
347
280
|
);
|
|
348
281
|
}
|
|
@@ -355,7 +288,7 @@ export const Geocoder = ({
|
|
|
355
288
|
aria-label="Clear search"
|
|
356
289
|
className="p-1 hover:bg-gray-100 rounded transition-colors"
|
|
357
290
|
>
|
|
358
|
-
<
|
|
291
|
+
<IoClose size={20} className="text-gray-500" />
|
|
359
292
|
</button>
|
|
360
293
|
);
|
|
361
294
|
}
|
|
@@ -369,7 +302,7 @@ export const Geocoder = ({
|
|
|
369
302
|
{open ? (
|
|
370
303
|
<div className="absolute z-1000 mt-1.5 w-full bg-white rounded shadow-lg overflow-hidden">
|
|
371
304
|
{results.length > 0 ? (
|
|
372
|
-
<div id={listboxId} role="listbox" className="max-h-60 overflow-auto">
|
|
305
|
+
<div id={listboxId} role="listbox" tabIndex={-1} className="max-h-60 overflow-auto">
|
|
373
306
|
{Object.entries(grouped).map(([group, items]) => (
|
|
374
307
|
<div key={group} role="presentation">
|
|
375
308
|
<div
|
|
@@ -382,21 +315,22 @@ export const Geocoder = ({
|
|
|
382
315
|
<div role="group">
|
|
383
316
|
{items.map((item) => {
|
|
384
317
|
flatIndex += 1;
|
|
385
|
-
const
|
|
318
|
+
const itemIndex = flatIndex;
|
|
319
|
+
const isActive = itemIndex === activeIndex;
|
|
386
320
|
const isSelected = item.id === selectedId;
|
|
387
321
|
|
|
388
322
|
return (
|
|
389
323
|
<div
|
|
390
|
-
id={`${listboxId}-option-${
|
|
324
|
+
id={`${listboxId}-option-${itemIndex}`}
|
|
391
325
|
key={item.id}
|
|
392
326
|
role="option"
|
|
393
327
|
aria-selected={isSelected}
|
|
394
328
|
tabIndex={-1}
|
|
395
|
-
className={`cursor-pointer px-3 py-2 text-sm text-gray-800
|
|
329
|
+
className={`scroll-mt-8 cursor-pointer px-3 py-2 text-sm text-gray-800
|
|
396
330
|
transition-colors ${isSelected ? 'bg-blue-50 font-semibold' : ''}
|
|
397
331
|
${isActive && !isSelected ? 'bg-gray-100' : ''}
|
|
398
332
|
${!isSelected && !isActive ? 'hover:bg-gray-50' : ''}`}
|
|
399
|
-
onMouseEnter={() => setActiveIndex(
|
|
333
|
+
onMouseEnter={() => setActiveIndex(itemIndex)}
|
|
400
334
|
onMouseDown={() => selectResult(item)}
|
|
401
335
|
>
|
|
402
336
|
<div className="truncate">{item.label}</div>
|