@mapfirst.ai/react 0.0.15 → 0.0.17
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/README.md +231 -298
- package/dist/index.d.mts +96 -295
- package/dist/index.d.ts +96 -295
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/EXAMPLES.md +0 -740
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
1
|
import * as _mapfirst_ai_core from '@mapfirst.ai/core';
|
|
3
|
-
import { PropertyType, PriceLevel, MapFirstCore, BaseMapFirstOptions, MapState, Property, MapLibreNamespace, GoogleMapsNamespace, MapboxNamespace
|
|
2
|
+
import { PropertyType, PriceLevel, MapFirstCore, BaseMapFirstOptions, MapState, Property, MapLibreNamespace, GoogleMapsNamespace, MapboxNamespace } from '@mapfirst.ai/core';
|
|
4
3
|
export { ApiFiltersResponse, convertToApiFilters, processApiFilters } from '@mapfirst.ai/core';
|
|
5
4
|
import * as React$1 from 'react';
|
|
6
5
|
import React__default, { FunctionComponent, CSSProperties, ReactNode } from 'react';
|
|
@@ -93,7 +92,6 @@ declare const Chip: React__default.FC<ChipProps>;
|
|
|
93
92
|
|
|
94
93
|
interface FilterChipsProps {
|
|
95
94
|
filters: Filter[];
|
|
96
|
-
isPortrait: boolean;
|
|
97
95
|
currency: string;
|
|
98
96
|
minRatingSuffix: string;
|
|
99
97
|
clearAllLabel: string;
|
|
@@ -146,12 +144,6 @@ declare const useFilterScroll: (dependency: number) => {
|
|
|
146
144
|
scrollByDir: (dir: "prev" | "next") => void;
|
|
147
145
|
};
|
|
148
146
|
|
|
149
|
-
/**
|
|
150
|
-
* Hook to detect if the viewport is in portrait orientation.
|
|
151
|
-
* Updates on window resize.
|
|
152
|
-
*/
|
|
153
|
-
declare const useIsPortrait: () => boolean;
|
|
154
|
-
|
|
155
147
|
type Locale = "en" | "es" | "de" | "fr" | "it" | "pt";
|
|
156
148
|
type TranslationFunction = (key: string, params?: Record<string, any>) => string;
|
|
157
149
|
type FormatCurrencyFunction = (value: number, currency?: string) => string;
|
|
@@ -201,14 +193,24 @@ type SmartFilter = {
|
|
|
201
193
|
priceLevels?: any[];
|
|
202
194
|
};
|
|
203
195
|
/**
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
* Returns the instance and reactive state that updates when SDK state changes.
|
|
196
|
+
* Comprehensive hook for MapFirst SDK with all functionality in one place.
|
|
197
|
+
* Creates a MapFirstCore instance with reactive state and provides all necessary methods.
|
|
207
198
|
*
|
|
208
199
|
* @example
|
|
209
200
|
* ```tsx
|
|
210
|
-
* //
|
|
211
|
-
* const {
|
|
201
|
+
* // Initialize with location data
|
|
202
|
+
* const {
|
|
203
|
+
* instance,
|
|
204
|
+
* state,
|
|
205
|
+
* setPrimaryType,
|
|
206
|
+
* setSelectedMarker,
|
|
207
|
+
* propertiesSearch,
|
|
208
|
+
* smartFilterSearch,
|
|
209
|
+
* boundsSearch,
|
|
210
|
+
* attachMapLibre,
|
|
211
|
+
* attachGoogle,
|
|
212
|
+
* attachMapbox
|
|
213
|
+
* } = useMapFirst({
|
|
212
214
|
* initialLocationData: {
|
|
213
215
|
* city: "New York",
|
|
214
216
|
* country: "United States",
|
|
@@ -217,298 +219,97 @@ type SmartFilter = {
|
|
|
217
219
|
* });
|
|
218
220
|
*
|
|
219
221
|
* // Access reactive state
|
|
220
|
-
* console.log(state
|
|
221
|
-
* console.log(state
|
|
222
|
+
* console.log(state?.properties);
|
|
223
|
+
* console.log(state?.isSearching);
|
|
224
|
+
* console.log(state?.selectedPropertyId);
|
|
222
225
|
*
|
|
223
|
-
* //
|
|
226
|
+
* // Attach map when ready
|
|
224
227
|
* useEffect(() => {
|
|
225
|
-
* if (mapLibreInstance
|
|
226
|
-
*
|
|
227
|
-
* platform: "maplibre",
|
|
228
|
-
* maplibregl: maplibregl,
|
|
228
|
+
* if (mapLibreInstance) {
|
|
229
|
+
* attachMapLibre(mapLibreInstance, maplibregl, {
|
|
229
230
|
* onMarkerClick: (marker) => console.log(marker)
|
|
230
231
|
* });
|
|
231
232
|
* }
|
|
232
|
-
* }, [mapLibreInstance
|
|
233
|
-
* ```
|
|
234
|
-
*/
|
|
235
|
-
declare function useMapFirstCore(options: BaseMapFirstOptions): {
|
|
236
|
-
mapFirst: MapFirstCore | null;
|
|
237
|
-
state: MapState | null;
|
|
238
|
-
};
|
|
239
|
-
/**
|
|
240
|
-
* Hook to access reactive properties from MapFirst SDK.
|
|
241
|
-
* Returns the current properties array that updates when properties change.
|
|
242
|
-
*
|
|
243
|
-
* @example
|
|
244
|
-
* ```tsx
|
|
245
|
-
* const { mapFirst } = useMapFirstCore({ ... });
|
|
246
|
-
* const properties = useMapFirstProperties(mapFirst);
|
|
247
|
-
*
|
|
248
|
-
* return <div>Found {properties.length} properties</div>;
|
|
249
|
-
* ```
|
|
250
|
-
*/
|
|
251
|
-
declare function useMapFirstProperties(mapFirst: MapFirstCore | null): Property[];
|
|
252
|
-
/**
|
|
253
|
-
* Hook to access the selected property ID from MapFirst SDK.
|
|
254
|
-
* Returns the currently selected property ID that updates when selection changes.
|
|
255
|
-
*
|
|
256
|
-
* @example
|
|
257
|
-
* ```tsx
|
|
258
|
-
* const { mapFirst } = useMapFirstCore({ ... });
|
|
259
|
-
* const selectedId = useMapFirstSelectedProperty(mapFirst);
|
|
260
|
-
*
|
|
261
|
-
* return <div>Selected: {selectedId || 'None'}</div>;
|
|
262
|
-
* ```
|
|
263
|
-
*/
|
|
264
|
-
declare function useMapFirstSelectedProperty(mapFirst: MapFirstCore | null): number | null;
|
|
265
|
-
/**
|
|
266
|
-
* Hook to access and control the primary property type.
|
|
267
|
-
* Returns the current primary type and a setter function.
|
|
268
|
-
*
|
|
269
|
-
* @example
|
|
270
|
-
* ```tsx
|
|
271
|
-
* const { mapFirst } = useMapFirstCore({ ... });
|
|
272
|
-
* const [primaryType, setPrimaryType] = usePrimaryType(mapFirst);
|
|
273
|
-
*
|
|
274
|
-
* return (
|
|
275
|
-
* <select value={primaryType} onChange={(e) => setPrimaryType(e.target.value as PropertyType)}>
|
|
276
|
-
* <option value="Accommodation">Hotels</option>
|
|
277
|
-
* <option value="Restaurant">Restaurants</option>
|
|
278
|
-
* <option value="Attraction">Attractions</option>
|
|
279
|
-
* </select>
|
|
280
|
-
* );
|
|
281
|
-
* ```
|
|
282
|
-
*/
|
|
283
|
-
declare function usePrimaryType(mapFirst: MapFirstCore | null): [PropertyType, (type: PropertyType) => void];
|
|
284
|
-
/**
|
|
285
|
-
* Hook to access and control the selected marker.
|
|
286
|
-
* Returns the current selected marker ID and a setter function.
|
|
287
|
-
* Note: This hook requires the MapFirstCore instance. For simpler usage with reactive updates,
|
|
288
|
-
* use state.selectedPropertyId from useMapFirstCore instead.
|
|
289
|
-
*
|
|
290
|
-
* @example
|
|
291
|
-
* ```tsx
|
|
292
|
-
* const { mapFirst } = useMapFirstCore({ ... });
|
|
293
|
-
* const [selectedMarker, setSelectedMarker] = useSelectedMarker(mapFirst);
|
|
294
|
-
*
|
|
295
|
-
* return (
|
|
296
|
-
* <div>
|
|
297
|
-
* <p>Selected: {selectedMarker || 'None'}</p>
|
|
298
|
-
* <button onClick={() => setSelectedMarker(null)}>Clear Selection</button>
|
|
299
|
-
* </div>
|
|
300
|
-
* );
|
|
301
|
-
* ```
|
|
302
|
-
*/
|
|
303
|
-
declare function useSelectedMarker(mapFirst: MapFirstCore | null): [number | null, (id: number | null) => void];
|
|
304
|
-
/**
|
|
305
|
-
* Hook for MapLibre GL JS integration.
|
|
306
|
-
* Automatically attaches the map when both the SDK instance and map are available.
|
|
307
|
-
*
|
|
308
|
-
* @example
|
|
309
|
-
* ```tsx
|
|
310
|
-
* const { mapFirst, state } = useMapFirstCore({ initialLocationData: { city: "Paris", country: "France" } });
|
|
311
|
-
* const mapRef = useRef<maplibregl.Map | null>(null);
|
|
312
|
-
*
|
|
313
|
-
* useMapLibreAttachment({
|
|
314
|
-
* mapFirst,
|
|
315
|
-
* map: mapRef.current,
|
|
316
|
-
* maplibregl: maplibregl,
|
|
317
|
-
* onMarkerClick: (marker) => console.log(marker)
|
|
318
|
-
* });
|
|
319
|
-
*
|
|
320
|
-
* // Access reactive state
|
|
321
|
-
* console.log(state?.properties);
|
|
322
|
-
* ```
|
|
323
|
-
*/
|
|
324
|
-
declare function useMapLibreAttachment({ mapFirst, map, maplibregl, onMarkerClick, }: {
|
|
325
|
-
mapFirst: MapFirstCore | null;
|
|
326
|
-
map: any | null;
|
|
327
|
-
maplibregl: MapLibreNamespace;
|
|
328
|
-
onMarkerClick?: (marker: Property) => void;
|
|
329
|
-
}): void;
|
|
330
|
-
/**
|
|
331
|
-
* Hook for Google Maps integration.
|
|
332
|
-
* Automatically attaches the map when both the SDK instance and map are available.
|
|
333
|
-
*
|
|
334
|
-
* @example
|
|
335
|
-
* ```tsx
|
|
336
|
-
* const { mapFirst, state } = useMapFirstCore({ initialLocationData: { city: "Tokyo", country: "Japan" } });
|
|
337
|
-
* const mapRef = useRef<google.maps.Map | null>(null);
|
|
233
|
+
* }, [mapLibreInstance]);
|
|
338
234
|
*
|
|
339
|
-
*
|
|
340
|
-
*
|
|
341
|
-
*
|
|
342
|
-
* google: window.google,
|
|
343
|
-
* onMarkerClick: (marker) => console.log(marker)
|
|
235
|
+
* // Use search methods
|
|
236
|
+
* await propertiesSearch.search({
|
|
237
|
+
* body: { city: "Paris", country: "France" }
|
|
344
238
|
* });
|
|
345
239
|
*
|
|
346
|
-
*
|
|
347
|
-
*
|
|
348
|
-
* ```
|
|
349
|
-
*/
|
|
350
|
-
declare function useGoogleMapsAttachment({ mapFirst, map, google, onMarkerClick, }: {
|
|
351
|
-
mapFirst: MapFirstCore | null;
|
|
352
|
-
map: any | null;
|
|
353
|
-
google: GoogleMapsNamespace;
|
|
354
|
-
onMarkerClick?: (marker: Property) => void;
|
|
355
|
-
}): void;
|
|
356
|
-
/**
|
|
357
|
-
* Hook for Mapbox GL JS integration.
|
|
358
|
-
* Automatically attaches the map when both the SDK instance and map are available.
|
|
359
|
-
*
|
|
360
|
-
* @example
|
|
361
|
-
* ```tsx
|
|
362
|
-
* const { mapFirst, state } = useMapFirstCore({ initialLocationData: { city: "London", country: "United Kingdom" } });
|
|
363
|
-
* const mapRef = useRef<mapboxgl.Map | null>(null);
|
|
364
|
-
*
|
|
365
|
-
* useMapboxAttachment({
|
|
366
|
-
* mapFirst,
|
|
367
|
-
* map: mapRef.current,
|
|
368
|
-
* mapboxgl: mapboxgl,
|
|
369
|
-
* onMarkerClick: (marker) => console.log(marker)
|
|
240
|
+
* await smartFilterSearch.search({
|
|
241
|
+
* query: "hotels near beach with pool"
|
|
370
242
|
* });
|
|
371
243
|
*
|
|
372
|
-
*
|
|
373
|
-
* console.log(state?.filters);
|
|
374
|
-
* ```
|
|
375
|
-
*/
|
|
376
|
-
declare function useMapboxAttachment({ mapFirst, map, mapboxgl, onMarkerClick, }: {
|
|
377
|
-
mapFirst: MapFirstCore | null;
|
|
378
|
-
map: any | null;
|
|
379
|
-
mapboxgl: MapboxNamespace;
|
|
380
|
-
onMarkerClick?: (marker: Property) => void;
|
|
381
|
-
}): void;
|
|
382
|
-
/**
|
|
383
|
-
* Legacy hook that creates the MapFirstCore instance with a map immediately.
|
|
384
|
-
* Use useMapFirstCore + useMap*Attachment hooks for better control.
|
|
385
|
-
*
|
|
386
|
-
* @deprecated Use useMapFirstCore and platform-specific attachment hooks instead
|
|
387
|
-
*/
|
|
388
|
-
declare function useMapFirst(options: MapFirstOptions | null): React__default.RefObject<MapFirstCore | null>;
|
|
389
|
-
/**
|
|
390
|
-
* Hook to run properties search with the MapFirst SDK.
|
|
391
|
-
* Returns a function to trigger the search and loading state.
|
|
392
|
-
*
|
|
393
|
-
* @example
|
|
394
|
-
* ```tsx
|
|
395
|
-
* const { mapFirst } = useMapFirstCore({ ... });
|
|
396
|
-
* const { search, isLoading, error } = usePropertiesSearch(mapFirst);
|
|
397
|
-
*
|
|
398
|
-
* const handleSearch = async () => {
|
|
399
|
-
* await search({
|
|
400
|
-
* body: {
|
|
401
|
-
* city: "Paris",
|
|
402
|
-
* country: "France",
|
|
403
|
-
* filters: {
|
|
404
|
-
* checkIn: new Date(),
|
|
405
|
-
* checkOut: new Date(Date.now() + 86400000),
|
|
406
|
-
* numAdults: 2,
|
|
407
|
-
* numRooms: 1
|
|
408
|
-
* }
|
|
409
|
-
* }
|
|
410
|
-
* });
|
|
411
|
-
* };
|
|
412
|
-
* ```
|
|
413
|
-
*/
|
|
414
|
-
declare function usePropertiesSearch(mapFirst: MapFirstCore | null): {
|
|
415
|
-
search: (options: {
|
|
416
|
-
body: InitialRequestBody;
|
|
417
|
-
beforeApplyProperties?: (data: any) => {
|
|
418
|
-
price?: any;
|
|
419
|
-
limit?: number;
|
|
420
|
-
};
|
|
421
|
-
smartFiltersClearable?: boolean;
|
|
422
|
-
}) => Promise<{
|
|
423
|
-
location_id?: number;
|
|
424
|
-
filters: _mapfirst_ai_core.FilterSchema;
|
|
425
|
-
properties: Property[];
|
|
426
|
-
isComplete: boolean | undefined;
|
|
427
|
-
pollingLink: string | undefined;
|
|
428
|
-
durationSeconds: number;
|
|
429
|
-
} | null>;
|
|
430
|
-
isLoading: boolean;
|
|
431
|
-
error: Error | null;
|
|
432
|
-
};
|
|
433
|
-
/**
|
|
434
|
-
* Hook to run smart filter search with the MapFirst SDK.
|
|
435
|
-
* Returns a function to trigger the search and loading state.
|
|
436
|
-
*
|
|
437
|
-
* @example
|
|
438
|
-
* ```tsx
|
|
439
|
-
* const { mapFirst } = useMapFirstCore({ ... });
|
|
440
|
-
* const { search, isLoading, error } = useSmartFilterSearch(mapFirst);
|
|
441
|
-
*
|
|
442
|
-
* const handleSearch = async () => {
|
|
443
|
-
* await search({
|
|
444
|
-
* query: "hotels near beach with pool"
|
|
445
|
-
* });
|
|
446
|
-
* };
|
|
447
|
-
*
|
|
448
|
-
* // Or with filters
|
|
449
|
-
* const handleFilterSearch = async () => {
|
|
450
|
-
* await search({
|
|
451
|
-
* filters: [
|
|
452
|
-
* { id: "pool", label: "Pool", type: "amenity", value: "pool" },
|
|
453
|
-
* { id: "4star", label: "4 Star", type: "starRating", value: "4", numericValue: 4 }
|
|
454
|
-
* ]
|
|
455
|
-
* });
|
|
456
|
-
* };
|
|
457
|
-
* ```
|
|
458
|
-
*/
|
|
459
|
-
declare function useSmartFilterSearch(mapFirst: MapFirstCore | null): {
|
|
460
|
-
search: (options: {
|
|
461
|
-
query?: string;
|
|
462
|
-
filters?: SmartFilter[];
|
|
463
|
-
onProcessFilters?: (filters: any, location_id?: number) => {
|
|
464
|
-
smartFilters?: SmartFilter[];
|
|
465
|
-
price?: any;
|
|
466
|
-
limit?: number;
|
|
467
|
-
language?: string;
|
|
468
|
-
};
|
|
469
|
-
}) => Promise<{
|
|
470
|
-
location_id?: number;
|
|
471
|
-
filters: _mapfirst_ai_core.FilterSchema;
|
|
472
|
-
properties: Property[];
|
|
473
|
-
isComplete: boolean | undefined;
|
|
474
|
-
pollingLink: string | undefined;
|
|
475
|
-
durationSeconds: number;
|
|
476
|
-
} | null>;
|
|
477
|
-
isLoading: boolean;
|
|
478
|
-
error: Error | null;
|
|
479
|
-
};
|
|
480
|
-
/**
|
|
481
|
-
* Hook to perform a bounds search when the user moves the map
|
|
482
|
-
*
|
|
483
|
-
* @example
|
|
484
|
-
* ```tsx
|
|
485
|
-
* const { mapFirst, state } = useMapFirstCore({ ... });
|
|
486
|
-
* const { performBoundsSearch, isSearching } = useMapFirstBoundsSearch(mapFirst);
|
|
487
|
-
*
|
|
488
|
-
* // When user clicks "Search this area" button
|
|
489
|
-
* <button onClick={performBoundsSearch} disabled={!state.pendingBounds || isSearching}>
|
|
490
|
-
* Search this area
|
|
491
|
-
* </button>
|
|
244
|
+
* await boundsSearch.perform();
|
|
492
245
|
* ```
|
|
493
246
|
*/
|
|
494
|
-
declare function
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
247
|
+
declare function useMapFirst(options: BaseMapFirstOptions): {
|
|
248
|
+
instance: MapFirstCore | null;
|
|
249
|
+
state: MapState | null;
|
|
250
|
+
setPrimaryType: (type: PropertyType) => void;
|
|
251
|
+
setSelectedMarker: (id: number | null) => void;
|
|
252
|
+
propertiesSearch: {
|
|
253
|
+
search: (options: {
|
|
254
|
+
body: InitialRequestBody;
|
|
255
|
+
beforeApplyProperties?: (data: any) => {
|
|
256
|
+
price?: any;
|
|
257
|
+
limit?: number;
|
|
258
|
+
};
|
|
259
|
+
smartFiltersClearable?: boolean;
|
|
260
|
+
}) => Promise<{
|
|
261
|
+
location_id?: number;
|
|
262
|
+
filters: _mapfirst_ai_core.FilterSchema;
|
|
263
|
+
properties: Property[];
|
|
264
|
+
isComplete: boolean | undefined;
|
|
265
|
+
pollingLink: string | undefined;
|
|
266
|
+
durationSeconds: number;
|
|
267
|
+
} | null>;
|
|
268
|
+
isLoading: boolean;
|
|
269
|
+
error: Error | null;
|
|
270
|
+
};
|
|
271
|
+
smartFilterSearch: {
|
|
272
|
+
search: (options: {
|
|
273
|
+
query?: string;
|
|
274
|
+
filters?: SmartFilter[];
|
|
275
|
+
onProcessFilters?: (filters: any, location_id?: number) => {
|
|
276
|
+
smartFilters?: SmartFilter[];
|
|
277
|
+
price?: any;
|
|
278
|
+
limit?: number;
|
|
279
|
+
language?: string;
|
|
280
|
+
};
|
|
281
|
+
}) => Promise<{
|
|
282
|
+
location_id?: number;
|
|
283
|
+
filters: _mapfirst_ai_core.FilterSchema;
|
|
284
|
+
properties: Property[];
|
|
285
|
+
isComplete: boolean | undefined;
|
|
286
|
+
pollingLink: string | undefined;
|
|
287
|
+
durationSeconds: number;
|
|
288
|
+
} | null>;
|
|
289
|
+
isLoading: boolean;
|
|
290
|
+
error: Error | null;
|
|
291
|
+
};
|
|
292
|
+
boundsSearch: {
|
|
293
|
+
perform: () => Promise<{
|
|
294
|
+
location_id?: number;
|
|
295
|
+
filters: _mapfirst_ai_core.FilterSchema;
|
|
296
|
+
properties: Property[];
|
|
297
|
+
isComplete: boolean | undefined;
|
|
298
|
+
pollingLink: string | undefined;
|
|
299
|
+
durationSeconds: number;
|
|
300
|
+
} | null>;
|
|
301
|
+
isSearching: boolean;
|
|
302
|
+
error: Error | null;
|
|
303
|
+
};
|
|
304
|
+
attachMapLibre: (map: any, maplibregl: MapLibreNamespace, options?: {
|
|
305
|
+
onMarkerClick?: (marker: Property) => void;
|
|
306
|
+
}) => void;
|
|
307
|
+
attachGoogle: (map: any, google: GoogleMapsNamespace, options?: {
|
|
308
|
+
onMarkerClick?: (marker: Property) => void;
|
|
309
|
+
}) => void;
|
|
310
|
+
attachMapbox: (map: any, mapboxgl: MapboxNamespace, options?: {
|
|
311
|
+
onMarkerClick?: (marker: Property) => void;
|
|
312
|
+
}) => void;
|
|
505
313
|
};
|
|
506
|
-
/**
|
|
507
|
-
* Helper component that simply renders the markers it receives so non-React environments
|
|
508
|
-
* can verify data flows before wiring the SDK into a map.
|
|
509
|
-
*/
|
|
510
|
-
declare function MarkerDebugList({ markers }: {
|
|
511
|
-
markers: Property[];
|
|
512
|
-
}): react_jsx_runtime.JSX.Element;
|
|
513
314
|
|
|
514
|
-
export { Chip, type ChipProps, CloseIcon, EditIcon, type Filter, FilterChips, type FilterChipsProps, type IconProps, type Locale,
|
|
315
|
+
export { Chip, type ChipProps, CloseIcon, EditIcon, type Filter, FilterChips, type FilterChipsProps, type IconProps, type Locale, MinRatingFilterChip, NextIcon, PriceRangeFilterChip, type PriceRangeValue, RestaurantPriceLevelChip, type RestaurantPriceLevelChipProps, SearchIcon, SmartFilter$1 as SmartFilter, type SmartFilterProps, StarIcon, TransformedQueryChip, type TransformedQueryChipProps, createMinRatingFilterLabel, createPriceRangeFilterLabel, formatRatingValue, renderStars, useFilterScroll, useMapFirst, useTranslation };
|