xertica-ui 1.3.7 → 1.3.9

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.
@@ -74,6 +74,8 @@ interface SidebarProps {
74
74
  location: { pathname: string };
75
75
  navigate: (path: string) => void;
76
76
  routes: RouteConfig[];
77
+ logo?: React.ReactNode;
78
+ logoCollapsed?: React.ReactNode;
77
79
  }
78
80
 
79
81
  export function Sidebar({
@@ -84,6 +86,8 @@ export function Sidebar({
84
86
  location,
85
87
  navigate,
86
88
  routes,
89
+ logo,
90
+ logoCollapsed,
87
91
  }: SidebarProps) {
88
92
  const navRef = useRef<HTMLDivElement>(null);
89
93
  const [hasOverflow, setHasOverflow] = useState(false);
@@ -318,15 +322,19 @@ export function Sidebar({
318
322
  >
319
323
  <div className="flex items-center justify-center flex-shrink-0">
320
324
  {expanded ? (
321
- <XerticaLogo
322
- className="h-5 w-auto"
323
- variant="white"
324
- />
325
+ logo || (
326
+ <XerticaLogo
327
+ className="h-5 w-auto"
328
+ variant="white"
329
+ />
330
+ )
325
331
  ) : (
326
- <XerticaXLogo
327
- className="h-5 w-auto"
328
- variant="white"
329
- />
332
+ logoCollapsed || (
333
+ <XerticaXLogo
334
+ className="h-5 w-auto"
335
+ variant="white"
336
+ />
337
+ )
330
338
  )}
331
339
  </div>
332
340
  </div>
@@ -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">
@@ -0,0 +1,71 @@
1
+ import React from 'react';
2
+ import { Map } from '../ui/map';
3
+ import { RouteMap } from '../ui/route-map';
4
+
5
+ export function ApiKeyMapExample() {
6
+ const [key, setKey] = React.useState('');
7
+ const [appliedKey, setAppliedKey] = React.useState('');
8
+
9
+ const handleApply = () => {
10
+ if (key) setAppliedKey(key);
11
+ };
12
+
13
+ return (
14
+ <div className="space-y-8 p-6">
15
+ <div className="space-y-4">
16
+ <h2 className="text-2xl font-bold">Map with Explicit API Key</h2>
17
+ <p className="text-muted-foreground">
18
+ Enter a Google Maps API Key to load the maps below. This simulates a scenario where the key is provided dynamically or via props, overriding/fallback for the global context.
19
+ </p>
20
+
21
+ <div className="flex gap-4 max-w-xl">
22
+ <input
23
+ type="text"
24
+ value={key}
25
+ onChange={(e) => setKey(e.target.value)}
26
+ placeholder="Enter Google Maps API Key"
27
+ className="flex-1 px-4 py-2 border rounded-md bg-background"
28
+ />
29
+ <button
30
+ onClick={handleApply}
31
+ className="px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90"
32
+ >
33
+ Load Maps
34
+ </button>
35
+ </div>
36
+ </div>
37
+
38
+ {appliedKey ? (
39
+ <div className="grid md:grid-cols-2 gap-8">
40
+ <div className="space-y-4">
41
+ <h3 className="font-semibold">Standard Map</h3>
42
+ <Map
43
+ apiKey={appliedKey}
44
+ height="400px"
45
+ center={{ lat: -23.5505, lng: -46.6333 }}
46
+ zoom={12}
47
+ markers={[
48
+ { position: { lat: -23.5505, lng: -46.6333 }, title: 'São Paulo' }
49
+ ]}
50
+ />
51
+ </div>
52
+
53
+ <div className="space-y-4">
54
+ <h3 className="font-semibold">Route Map</h3>
55
+ <RouteMap
56
+ apiKey={appliedKey}
57
+ height="400px"
58
+ origin={{ lat: -23.5505, lng: -46.6333 }}
59
+ destination={{ lat: -23.6005, lng: -46.6833 }}
60
+ travelMode="DRIVING"
61
+ />
62
+ </div>
63
+ </div>
64
+ ) : (
65
+ <div className="p-12 border-2 border-dashed rounded-lg text-center text-muted-foreground">
66
+ Enter an API Key above to render the maps.
67
+ </div>
68
+ )}
69
+ </div>
70
+ );
71
+ }
@@ -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 function DrawingMapExample() {
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
- toast.info('Posição do marcador atualizada');
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
- mapInstance.setOptions({ draggableCursor: 'grab', clickableIcons: true });
201
- return;
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
- cancelPolygonDrawing();
253
+ cancelPolygonDrawing();
250
254
  }
251
255
  setSelectedMode(mode);
252
256
  };
253
257
 
254
258
  const clearAll = () => {
255
- shapes.forEach(shape => shape.overlay.setMap(null));
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
- cancelPolygonDrawing();
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 function FilterableMapExample() {
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
- { lat: -23.540, lng: -46.620 },
185
- { lat: -23.540, lng: -46.660 },
186
- { lat: -23.565, lng: -46.660 },
187
- { lat: -23.565, lng: -46.620 }
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>&lt;gmp-map&gt;</code> and <code>&lt;gmp-advanced-marker&gt;</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 (&lt;gmp-map&gt;)</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
+ }