xertica-ui 1.3.8 → 1.4.0
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/components/TemplateContent.tsx +7 -0
- package/components/examples/DrawingMapExample.tsx +58 -49
- package/components/examples/FilterableMapExample.tsx +13 -8
- package/components/examples/MapExamples.tsx +8 -6
- package/components/examples/MapGmpExample.tsx +153 -0
- package/components/examples/MapShowcase.tsx +53 -27
- package/components/ui/alert.tsx +15 -8
- package/components/ui/index.ts +1 -0
- package/components/ui/map-config.ts +1 -1
- package/components/ui/map.tsx +200 -169
- package/components/ui/route-map.tsx +69 -44
- package/contexts/BrandColorsContext.tsx +5 -5
- package/dist/components/examples/DrawingMapExample.d.ts +4 -0
- package/dist/components/examples/FilterableMapExample.d.ts +53 -0
- package/dist/components/examples/MapShowcase.d.ts +9 -0
- package/dist/components/examples/SimpleFilterableMap.d.ts +1 -0
- package/dist/components/ui/alert.d.ts +4 -1
- package/dist/components/ui/index.d.ts +1 -0
- package/dist/components/ui/map-config.d.ts +1 -1
- package/dist/components/ui/map.d.ts +17 -0
- package/dist/components/ui/route-map.d.ts +1 -0
- package/dist/index.es.js +1833 -459
- package/dist/index.umd.js +1833 -459
- package/index.css +1 -1
- package/package.json +1 -1
- package/tsconfig.json +2 -1
|
@@ -20,6 +20,7 @@ import { toast } from 'sonner';
|
|
|
20
20
|
import { ScrollArea } from './ui/scroll-area';
|
|
21
21
|
import { ThemeToggle } from './ThemeToggle';
|
|
22
22
|
import { LanguageSelector } from './LanguageSelector';
|
|
23
|
+
import { MapShowcase } from './examples/MapShowcase';
|
|
23
24
|
|
|
24
25
|
import { useLayout } from '../contexts/LayoutContext';
|
|
25
26
|
|
|
@@ -597,6 +598,12 @@ export function TemplateContent({ }: TemplateContentProps) {
|
|
|
597
598
|
</CardContent>
|
|
598
599
|
</Card>
|
|
599
600
|
</section>
|
|
601
|
+
{/* Maps */}
|
|
602
|
+
<section>
|
|
603
|
+
<MapShowcase />
|
|
604
|
+
</section>
|
|
605
|
+
|
|
606
|
+
<Separator className="my-8" />
|
|
600
607
|
|
|
601
608
|
{/* Footer Note */}
|
|
602
609
|
<Card className="mt-8">
|
|
@@ -4,14 +4,14 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../ui
|
|
|
4
4
|
import { Button } from '../ui/button';
|
|
5
5
|
import { Badge } from '../ui/badge';
|
|
6
6
|
import { Separator } from '../ui/separator';
|
|
7
|
-
import {
|
|
8
|
-
MousePointer2,
|
|
9
|
-
MapPin,
|
|
10
|
-
Circle,
|
|
11
|
-
Square,
|
|
12
|
-
Hexagon,
|
|
13
|
-
Trash2,
|
|
14
|
-
Save,
|
|
7
|
+
import {
|
|
8
|
+
MousePointer2,
|
|
9
|
+
MapPin,
|
|
10
|
+
Circle,
|
|
11
|
+
Square,
|
|
12
|
+
Hexagon,
|
|
13
|
+
Trash2,
|
|
14
|
+
Save,
|
|
15
15
|
Undo,
|
|
16
16
|
Check,
|
|
17
17
|
X
|
|
@@ -30,13 +30,17 @@ type DrawnShape = {
|
|
|
30
30
|
data?: any;
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
export
|
|
33
|
+
export interface DrawingMapExampleProps {
|
|
34
|
+
apiKey?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function DrawingMapExample({ apiKey }: DrawingMapExampleProps) {
|
|
34
38
|
const [mapInstance, setMapInstance] = useState<google.maps.Map | null>(null);
|
|
35
39
|
const [selectedMode, setSelectedMode] = useState<DrawingMode>(null);
|
|
36
40
|
const [shapes, setShapes] = useState<DrawnShape[]>([]);
|
|
37
41
|
const [showSaveDialog, setShowSaveDialog] = useState(false);
|
|
38
42
|
const [savedData, setSavedData] = useState('');
|
|
39
|
-
|
|
43
|
+
|
|
40
44
|
// Estado para desenho de polígono em andamento
|
|
41
45
|
const [isDrawingPolygon, setIsDrawingPolygon] = useState(false);
|
|
42
46
|
const tempPolylineRef = useRef<google.maps.Polyline | null>(null);
|
|
@@ -48,7 +52,7 @@ export function DrawingMapExample() {
|
|
|
48
52
|
// Cores do Design System (Xertica Primary)
|
|
49
53
|
const colors = {
|
|
50
54
|
fill: '#2C275B', // Primary do CSS
|
|
51
|
-
stroke: '#2C275B',
|
|
55
|
+
stroke: '#2C275B',
|
|
52
56
|
fillOpacity: 0.2,
|
|
53
57
|
strokeWeight: 2,
|
|
54
58
|
editable: true,
|
|
@@ -69,7 +73,7 @@ export function DrawingMapExample() {
|
|
|
69
73
|
});
|
|
70
74
|
|
|
71
75
|
marker.addListener('dragend', () => {
|
|
72
|
-
|
|
76
|
+
toast.info('Posição do marcador atualizada');
|
|
73
77
|
});
|
|
74
78
|
|
|
75
79
|
const newShape: DrawnShape = { type: 'marker', overlay: marker };
|
|
@@ -154,7 +158,7 @@ export function DrawingMapExample() {
|
|
|
154
158
|
|
|
155
159
|
const newShape: DrawnShape = { type: 'polygon', overlay: polygon };
|
|
156
160
|
setShapes(prev => [...prev, newShape]);
|
|
157
|
-
|
|
161
|
+
|
|
158
162
|
cancelPolygonDrawing();
|
|
159
163
|
toast.success('Polígono criado com sucesso!');
|
|
160
164
|
}, [mapInstance, colors, cancelPolygonDrawing]);
|
|
@@ -165,7 +169,7 @@ export function DrawingMapExample() {
|
|
|
165
169
|
if (!isDrawingPolygon) {
|
|
166
170
|
setIsDrawingPolygon(true);
|
|
167
171
|
polygonPathRef.current = [point];
|
|
168
|
-
|
|
172
|
+
|
|
169
173
|
tempPolylineRef.current = new google.maps.Polyline({
|
|
170
174
|
map: mapInstance,
|
|
171
175
|
path: polygonPathRef.current,
|
|
@@ -175,7 +179,7 @@ export function DrawingMapExample() {
|
|
|
175
179
|
geodesic: true,
|
|
176
180
|
clickable: false // Importante: não capturar cliques para não atrapalhar o mapa
|
|
177
181
|
});
|
|
178
|
-
|
|
182
|
+
|
|
179
183
|
toast.info('Clique para adicionar pontos. Duplo clique ou botão check para fechar.', { duration: 4000 });
|
|
180
184
|
} else {
|
|
181
185
|
polygonPathRef.current.push(point);
|
|
@@ -197,13 +201,13 @@ export function DrawingMapExample() {
|
|
|
197
201
|
clearListeners();
|
|
198
202
|
|
|
199
203
|
if (!selectedMode) {
|
|
200
|
-
|
|
201
|
-
|
|
204
|
+
mapInstance.setOptions({ draggableCursor: 'grab', clickableIcons: true });
|
|
205
|
+
return;
|
|
202
206
|
}
|
|
203
207
|
|
|
204
|
-
mapInstance.setOptions({
|
|
208
|
+
mapInstance.setOptions({
|
|
205
209
|
draggableCursor: 'crosshair',
|
|
206
|
-
clickableIcons: false
|
|
210
|
+
clickableIcons: false
|
|
207
211
|
});
|
|
208
212
|
|
|
209
213
|
const clickListener = mapInstance.addListener('click', (e: google.maps.MapMouseEvent) => {
|
|
@@ -246,13 +250,16 @@ export function DrawingMapExample() {
|
|
|
246
250
|
|
|
247
251
|
const handleModeChange = (mode: DrawingMode) => {
|
|
248
252
|
if (isDrawingPolygon) {
|
|
249
|
-
|
|
253
|
+
cancelPolygonDrawing();
|
|
250
254
|
}
|
|
251
255
|
setSelectedMode(mode);
|
|
252
256
|
};
|
|
253
257
|
|
|
254
258
|
const clearAll = () => {
|
|
255
|
-
shapes.forEach(shape =>
|
|
259
|
+
shapes.forEach(shape => {
|
|
260
|
+
if ('setMap' in shape.overlay) (shape.overlay as any).setMap(null);
|
|
261
|
+
else (shape.overlay as any).map = null;
|
|
262
|
+
});
|
|
256
263
|
setShapes([]);
|
|
257
264
|
if (isDrawingPolygon) cancelPolygonDrawing();
|
|
258
265
|
toast.info('Mapa limpo');
|
|
@@ -264,16 +271,17 @@ export function DrawingMapExample() {
|
|
|
264
271
|
polygonPathRef.current.pop();
|
|
265
272
|
tempPolylineRef.current?.setPath(polygonPathRef.current);
|
|
266
273
|
if (polygonPathRef.current.length === 0) {
|
|
267
|
-
|
|
274
|
+
cancelPolygonDrawing();
|
|
268
275
|
}
|
|
269
276
|
}
|
|
270
277
|
return;
|
|
271
278
|
}
|
|
272
279
|
|
|
273
280
|
if (shapes.length === 0) return;
|
|
274
|
-
|
|
281
|
+
|
|
275
282
|
const lastShape = shapes[shapes.length - 1];
|
|
276
|
-
lastShape.overlay.setMap(null);
|
|
283
|
+
if ('setMap' in lastShape.overlay) (lastShape.overlay as any).setMap(null);
|
|
284
|
+
else (lastShape.overlay as any).map = null;
|
|
277
285
|
setShapes(prev => prev.slice(0, prev.length - 1));
|
|
278
286
|
};
|
|
279
287
|
|
|
@@ -285,7 +293,7 @@ export function DrawingMapExample() {
|
|
|
285
293
|
|
|
286
294
|
const exportData = shapes.map(s => {
|
|
287
295
|
let data: any = {};
|
|
288
|
-
|
|
296
|
+
|
|
289
297
|
if (s.type === 'marker') {
|
|
290
298
|
const marker = s.overlay as google.maps.marker.AdvancedMarkerElement;
|
|
291
299
|
const pos = marker.position;
|
|
@@ -295,7 +303,7 @@ export function DrawingMapExample() {
|
|
|
295
303
|
const lng = typeof (pos as any).lng === 'function' ? (pos as any).lng() : (pos as any).lng;
|
|
296
304
|
data = { lat, lng };
|
|
297
305
|
}
|
|
298
|
-
}
|
|
306
|
+
}
|
|
299
307
|
else if (s.type === 'circle') {
|
|
300
308
|
const circle = s.overlay as google.maps.Circle;
|
|
301
309
|
data = {
|
|
@@ -327,16 +335,16 @@ export function DrawingMapExample() {
|
|
|
327
335
|
};
|
|
328
336
|
|
|
329
337
|
// Botão auxiliar para renderizar itens da toolbar
|
|
330
|
-
const ToolButton = ({
|
|
331
|
-
active,
|
|
332
|
-
onClick,
|
|
333
|
-
icon: Icon,
|
|
334
|
-
label
|
|
335
|
-
}: {
|
|
336
|
-
active?: boolean;
|
|
337
|
-
onClick: () => void;
|
|
338
|
-
icon: any;
|
|
339
|
-
label: string
|
|
338
|
+
const ToolButton = ({
|
|
339
|
+
active,
|
|
340
|
+
onClick,
|
|
341
|
+
icon: Icon,
|
|
342
|
+
label
|
|
343
|
+
}: {
|
|
344
|
+
active?: boolean;
|
|
345
|
+
onClick: () => void;
|
|
346
|
+
icon: any;
|
|
347
|
+
label: string
|
|
340
348
|
}) => (
|
|
341
349
|
<Button
|
|
342
350
|
variant={active ? "default" : "ghost"}
|
|
@@ -363,7 +371,7 @@ export function DrawingMapExample() {
|
|
|
363
371
|
</Badge>
|
|
364
372
|
</div>
|
|
365
373
|
</CardHeader>
|
|
366
|
-
|
|
374
|
+
|
|
367
375
|
<CardContent className="p-0 flex flex-col md:flex-row h-[600px] bg-background">
|
|
368
376
|
{/* Sidebar de Ferramentas */}
|
|
369
377
|
<div className="w-full md:w-64 border-b md:border-b-0 md:border-r bg-muted/10 flex flex-col">
|
|
@@ -375,31 +383,31 @@ export function DrawingMapExample() {
|
|
|
375
383
|
Ferramentas
|
|
376
384
|
</h4>
|
|
377
385
|
<div className="space-y-1">
|
|
378
|
-
<ToolButton
|
|
386
|
+
<ToolButton
|
|
379
387
|
active={selectedMode === null}
|
|
380
388
|
onClick={() => handleModeChange(null)}
|
|
381
389
|
icon={MousePointer2}
|
|
382
390
|
label="Navegar"
|
|
383
391
|
/>
|
|
384
|
-
<ToolButton
|
|
392
|
+
<ToolButton
|
|
385
393
|
active={selectedMode === 'marker'}
|
|
386
394
|
onClick={() => handleModeChange('marker')}
|
|
387
395
|
icon={MapPin}
|
|
388
396
|
label="Marcador"
|
|
389
397
|
/>
|
|
390
|
-
<ToolButton
|
|
398
|
+
<ToolButton
|
|
391
399
|
active={selectedMode === 'circle'}
|
|
392
400
|
onClick={() => handleModeChange('circle')}
|
|
393
401
|
icon={Circle}
|
|
394
402
|
label="Círculo"
|
|
395
403
|
/>
|
|
396
|
-
<ToolButton
|
|
404
|
+
<ToolButton
|
|
397
405
|
active={selectedMode === 'rectangle'}
|
|
398
406
|
onClick={() => handleModeChange('rectangle')}
|
|
399
407
|
icon={Square}
|
|
400
408
|
label="Retângulo"
|
|
401
409
|
/>
|
|
402
|
-
<ToolButton
|
|
410
|
+
<ToolButton
|
|
403
411
|
active={selectedMode === 'polygon'}
|
|
404
412
|
onClick={() => handleModeChange('polygon')}
|
|
405
413
|
icon={Hexagon}
|
|
@@ -436,7 +444,7 @@ export function DrawingMapExample() {
|
|
|
436
444
|
Limpar
|
|
437
445
|
</Button>
|
|
438
446
|
</div>
|
|
439
|
-
|
|
447
|
+
|
|
440
448
|
<Button
|
|
441
449
|
variant="default"
|
|
442
450
|
onClick={handleSave}
|
|
@@ -461,16 +469,16 @@ export function DrawingMapExample() {
|
|
|
461
469
|
Desenhando Polígono...
|
|
462
470
|
</div>
|
|
463
471
|
<div className="flex gap-2">
|
|
464
|
-
<Button
|
|
465
|
-
size="sm"
|
|
472
|
+
<Button
|
|
473
|
+
size="sm"
|
|
466
474
|
className="w-full bg-green-600 hover:bg-green-700 text-white"
|
|
467
475
|
onClick={finishPolygon}
|
|
468
476
|
>
|
|
469
477
|
<Check className="h-3 w-3 mr-1" /> Concluir
|
|
470
478
|
</Button>
|
|
471
|
-
<Button
|
|
472
|
-
size="sm"
|
|
473
|
-
variant="outline"
|
|
479
|
+
<Button
|
|
480
|
+
size="sm"
|
|
481
|
+
variant="outline"
|
|
474
482
|
className="w-full"
|
|
475
483
|
onClick={cancelPolygonDrawing}
|
|
476
484
|
>
|
|
@@ -492,8 +500,9 @@ export function DrawingMapExample() {
|
|
|
492
500
|
disableDefaultUI={true}
|
|
493
501
|
zoomControl={true}
|
|
494
502
|
onMapLoad={setMapInstance}
|
|
503
|
+
apiKey={apiKey}
|
|
495
504
|
/>
|
|
496
|
-
|
|
505
|
+
|
|
497
506
|
{/* Instruções Flutuantes */}
|
|
498
507
|
{selectedMode && !isDrawingPolygon && (
|
|
499
508
|
<div className="absolute bottom-6 left-1/2 -translate-x-1/2 bg-background/90 backdrop-blur border px-4 py-2 rounded-full shadow-sm text-xs font-medium text-muted-foreground pointer-events-none z-10">
|
|
@@ -24,7 +24,7 @@ function createLucideIconSvg(iconName: string): string {
|
|
|
24
24
|
shopping: '<path d="M6 2 3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4Z"></path><path d="M3 6h18"></path><path d="M16 10a4 4 0 0 1-8 0"></path>',
|
|
25
25
|
coffee: '<path d="M17 8h1a4 4 0 1 1 0 8h-1"></path><path d="M3 8h14v9a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4Z"></path><line x1="6" x2="6" y1="2" y2="4"></line><line x1="10" x2="10" y1="2" y2="4"></line><line x1="14" x2="14" y1="2" y2="4"></line>',
|
|
26
26
|
};
|
|
27
|
-
|
|
27
|
+
|
|
28
28
|
const path = iconPaths[iconName] || '';
|
|
29
29
|
return `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">${path}</svg>`;
|
|
30
30
|
}
|
|
@@ -167,7 +167,11 @@ const sampleLocations = [
|
|
|
167
167
|
* Permite filtrar marcadores por categorias com interface integrada no mapa
|
|
168
168
|
* COMPACTO: Usa checkboxes para economia de espaço
|
|
169
169
|
*/
|
|
170
|
-
export
|
|
170
|
+
export interface FilterableMapExampleProps {
|
|
171
|
+
apiKey?: string;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function FilterableMapExample({ apiKey }: FilterableMapExampleProps) {
|
|
171
175
|
const [activeFilters, setActiveFilters] = useState<Set<MarkerGroupId>>(
|
|
172
176
|
new Set(Object.keys(markerGroups) as MarkerGroupId[])
|
|
173
177
|
);
|
|
@@ -224,7 +228,7 @@ export function FilterableMapExample() {
|
|
|
224
228
|
{filteredLocations.length} de {sampleLocations.length} locais
|
|
225
229
|
</Badge>
|
|
226
230
|
</div>
|
|
227
|
-
|
|
231
|
+
|
|
228
232
|
<p className="text-muted-foreground">
|
|
229
233
|
Use os filtros compactos no canto superior esquerdo do mapa para visualizar marcadores por categoria
|
|
230
234
|
</p>
|
|
@@ -238,6 +242,7 @@ export function FilterableMapExample() {
|
|
|
238
242
|
height="500px"
|
|
239
243
|
zoomControl={true}
|
|
240
244
|
fullscreenControl={true}
|
|
245
|
+
apiKey={apiKey}
|
|
241
246
|
/>
|
|
242
247
|
{/* Painel de Filtros COMPACTO Integrado no Mapa */}
|
|
243
248
|
<div
|
|
@@ -246,7 +251,7 @@ export function FilterableMapExample() {
|
|
|
246
251
|
showFilters ? "translate-x-0" : "-translate-x-full"
|
|
247
252
|
)}
|
|
248
253
|
>
|
|
249
|
-
<div
|
|
254
|
+
<div
|
|
250
255
|
className="p-2.5 rounded-lg shadow-[var(--shadow-elevation-sm)] bg-card border border-border max-w-[220px]"
|
|
251
256
|
>
|
|
252
257
|
<div className="flex items-center justify-between mb-2 gap-2">
|
|
@@ -295,12 +300,12 @@ export function FilterableMapExample() {
|
|
|
295
300
|
checked={isActive}
|
|
296
301
|
onCheckedChange={() => toggleFilter(groupId)}
|
|
297
302
|
/>
|
|
298
|
-
<Label
|
|
299
|
-
htmlFor={`filter-${id}`}
|
|
303
|
+
<Label
|
|
304
|
+
htmlFor={`filter-${id}`}
|
|
300
305
|
className="flex items-center gap-1.5 cursor-pointer text-sm"
|
|
301
306
|
>
|
|
302
|
-
<Icon
|
|
303
|
-
className="w-3.5 h-3.5"
|
|
307
|
+
<Icon
|
|
308
|
+
className="w-3.5 h-3.5"
|
|
304
309
|
style={{ color: group.color }}
|
|
305
310
|
/>
|
|
306
311
|
<span className={isActive ? "text-foreground" : "text-muted-foreground"}>
|
|
@@ -13,7 +13,7 @@ import { MapPin, Navigation, Store, Heart } from 'lucide-react';
|
|
|
13
13
|
// 1. Mapa de Loja com Raio de Entrega
|
|
14
14
|
export function StoreDeliveryMap() {
|
|
15
15
|
const storeLocation = { lat: -23.5505, lng: -46.6333 };
|
|
16
|
-
|
|
16
|
+
|
|
17
17
|
return (
|
|
18
18
|
<Card>
|
|
19
19
|
<CardHeader>
|
|
@@ -181,10 +181,12 @@ export function CompanyOfficesMap() {
|
|
|
181
181
|
export function ServiceZoneMap() {
|
|
182
182
|
const serviceZone = {
|
|
183
183
|
paths: [
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
184
|
+
[
|
|
185
|
+
{ lat: -23.540, lng: -46.620 },
|
|
186
|
+
{ lat: -23.540, lng: -46.660 },
|
|
187
|
+
{ lat: -23.565, lng: -46.660 },
|
|
188
|
+
{ lat: -23.565, lng: -46.620 }
|
|
189
|
+
]
|
|
188
190
|
],
|
|
189
191
|
fillColor: "#3B82F6",
|
|
190
192
|
strokeColor: "#2563EB"
|
|
@@ -214,7 +216,7 @@ export function ServiceZoneMap() {
|
|
|
214
216
|
/>
|
|
215
217
|
<div className="mt-4 p-4 bg-blue-50 dark:bg-blue-950/20 rounded-lg border border-blue-200 dark:border-blue-800">
|
|
216
218
|
<p className="text-sm text-blue-900 dark:text-blue-100">
|
|
217
|
-
<strong>Atendimento prioritário:</strong> Clientes dentro da zona azul
|
|
219
|
+
<strong>Atendimento prioritário:</strong> Clientes dentro da zona azul
|
|
218
220
|
recebem atendimento em até 2 horas.
|
|
219
221
|
</p>
|
|
220
222
|
</div>
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Map, MapProps } from '../ui/map';
|
|
3
|
+
import { RouteMap } from '../ui/route-map';
|
|
4
|
+
import { Button } from '../ui/button';
|
|
5
|
+
import { Input } from '../ui/input';
|
|
6
|
+
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '../ui/card';
|
|
7
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from '../ui/tabs';
|
|
8
|
+
|
|
9
|
+
export function MapGmpExample() {
|
|
10
|
+
const [apiKey, setApiKey] = useState('');
|
|
11
|
+
|
|
12
|
+
// Example markers
|
|
13
|
+
const markers = [
|
|
14
|
+
{
|
|
15
|
+
position: { lat: -23.5505, lng: -46.6333 },
|
|
16
|
+
title: "São Paulo",
|
|
17
|
+
info: "Capital financeira",
|
|
18
|
+
customColor: "#EF4444",
|
|
19
|
+
icon: "SP"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
position: { lat: -23.58, lng: -46.65 },
|
|
23
|
+
title: "Ibirapuera",
|
|
24
|
+
info: "Parque do Ibirapuera",
|
|
25
|
+
customColor: "#10B981",
|
|
26
|
+
icon: "IB"
|
|
27
|
+
}
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<div className="container mx-auto py-8 space-y-8">
|
|
32
|
+
<div className="space-y-4">
|
|
33
|
+
<h1 className="text-3xl font-bold">Google Maps Platform - Web Components</h1>
|
|
34
|
+
<p className="text-muted-foreground">
|
|
35
|
+
This example uses custom elements <code><gmp-map></code> and <code><gmp-advanced-marker></code> for better performance and modern integration.
|
|
36
|
+
</p>
|
|
37
|
+
|
|
38
|
+
<Card>
|
|
39
|
+
<CardHeader>
|
|
40
|
+
<CardTitle>API Key Configuration</CardTitle>
|
|
41
|
+
<CardDescription>Enter your API Key to load the maps. It will be stored in the session context.</CardDescription>
|
|
42
|
+
</CardHeader>
|
|
43
|
+
<CardContent>
|
|
44
|
+
<div className="flex gap-4">
|
|
45
|
+
<Input
|
|
46
|
+
type="password"
|
|
47
|
+
placeholder="Paste your Google Maps API Key here"
|
|
48
|
+
value={apiKey}
|
|
49
|
+
onChange={(e) => setApiKey(e.target.value)}
|
|
50
|
+
className="flex-1"
|
|
51
|
+
/>
|
|
52
|
+
</div>
|
|
53
|
+
</CardContent>
|
|
54
|
+
</Card>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<Tabs defaultValue="standard">
|
|
58
|
+
<TabsList className="mb-4">
|
|
59
|
+
<TabsTrigger value="standard">Standard Map</TabsTrigger>
|
|
60
|
+
<TabsTrigger value="satellite">Satellite</TabsTrigger>
|
|
61
|
+
<TabsTrigger value="dark">Dark / NY</TabsTrigger>
|
|
62
|
+
<TabsTrigger value="route">Route Map</TabsTrigger>
|
|
63
|
+
</TabsList>
|
|
64
|
+
|
|
65
|
+
<TabsContent value="standard" className="space-y-4">
|
|
66
|
+
<Card>
|
|
67
|
+
<CardHeader>
|
|
68
|
+
<CardTitle>Standard Map (<gmp-map>)</CardTitle>
|
|
69
|
+
</CardHeader>
|
|
70
|
+
<CardContent>
|
|
71
|
+
<Map
|
|
72
|
+
apiKey={apiKey}
|
|
73
|
+
height="500px"
|
|
74
|
+
zoom={12}
|
|
75
|
+
center={{ lat: -23.56, lng: -46.64 }}
|
|
76
|
+
markers={markers}
|
|
77
|
+
circle={{
|
|
78
|
+
center: { lat: -23.5505, lng: -46.6333 },
|
|
79
|
+
radius: 1000,
|
|
80
|
+
fillColor: "#EF4444"
|
|
81
|
+
}}
|
|
82
|
+
/>
|
|
83
|
+
</CardContent>
|
|
84
|
+
</Card>
|
|
85
|
+
</TabsContent>
|
|
86
|
+
|
|
87
|
+
<TabsContent value="route" className="space-y-4">
|
|
88
|
+
<Card>
|
|
89
|
+
<CardHeader>
|
|
90
|
+
<CardTitle>Route Map (Hybrid)</CardTitle>
|
|
91
|
+
</CardHeader>
|
|
92
|
+
<CardContent>
|
|
93
|
+
<RouteMap
|
|
94
|
+
apiKey={apiKey}
|
|
95
|
+
height="500px"
|
|
96
|
+
origin={{ lat: -23.5505, lng: -46.6333 }}
|
|
97
|
+
destination={{ lat: -23.58, lng: -46.65 }}
|
|
98
|
+
travelMode="DRIVING"
|
|
99
|
+
onRouteCalculated={(distance, duration) => console.log(`Route: ${distance}, ${duration}`)}
|
|
100
|
+
/>
|
|
101
|
+
</CardContent>
|
|
102
|
+
</Card>
|
|
103
|
+
</TabsContent>
|
|
104
|
+
<TabsContent value="satellite" className="space-y-4">
|
|
105
|
+
<Card>
|
|
106
|
+
<CardHeader>
|
|
107
|
+
<CardTitle>Satellite View</CardTitle>
|
|
108
|
+
</CardHeader>
|
|
109
|
+
<CardContent>
|
|
110
|
+
<Map
|
|
111
|
+
apiKey={apiKey}
|
|
112
|
+
height="500px"
|
|
113
|
+
zoom={16}
|
|
114
|
+
center={{ lat: -23.5505, lng: -46.6333 }}
|
|
115
|
+
mapTypeId="satellite"
|
|
116
|
+
markers={[{
|
|
117
|
+
position: { lat: -23.5505, lng: -46.6333 },
|
|
118
|
+
title: "Detailed View",
|
|
119
|
+
customColor: "#ffffff",
|
|
120
|
+
iconColor: "#000000",
|
|
121
|
+
icon: "👁️"
|
|
122
|
+
}]}
|
|
123
|
+
/>
|
|
124
|
+
</CardContent>
|
|
125
|
+
</Card>
|
|
126
|
+
</TabsContent>
|
|
127
|
+
|
|
128
|
+
<TabsContent value="dark" className="space-y-4">
|
|
129
|
+
<Card>
|
|
130
|
+
<CardHeader>
|
|
131
|
+
<CardTitle>Dark Mode (Styled)</CardTitle>
|
|
132
|
+
</CardHeader>
|
|
133
|
+
<CardContent>
|
|
134
|
+
<Map
|
|
135
|
+
apiKey={apiKey}
|
|
136
|
+
height="500px"
|
|
137
|
+
zoom={12}
|
|
138
|
+
center={{ lat: 40.7128, lng: -74.0060 }}
|
|
139
|
+
mapId="4504f8b37365c3d0" // Example Dark Map ID (won't work without config, falling back to standard if invalid)
|
|
140
|
+
// Alternatively use styles prop if we supported legacy styles, but GMP recommends MapID.
|
|
141
|
+
// For now, let's just show a different location and markers to varify reusability
|
|
142
|
+
markers={[
|
|
143
|
+
{ position: { lat: 40.7128, lng: -74.0060 }, title: "New York", customColor: "#3b82f6" },
|
|
144
|
+
{ position: { lat: 40.7580, lng: -73.9855 }, title: "Times Square", customColor: "#8b5cf6" }
|
|
145
|
+
]}
|
|
146
|
+
/>
|
|
147
|
+
</CardContent>
|
|
148
|
+
</Card>
|
|
149
|
+
</TabsContent>
|
|
150
|
+
</Tabs>
|
|
151
|
+
</div>
|
|
152
|
+
);
|
|
153
|
+
}
|