@geops/rvf-mobility-web-component 0.1.13 → 0.1.14

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.
Files changed (82) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/index.js +217 -112
  3. package/package.json +5 -3
  4. package/src/RvfButton/RvfButton.tsx +5 -2
  5. package/src/RvfCheckbox/RvfCheckbox.tsx +10 -4
  6. package/src/RvfExportMenu/RvfExportMenu.tsx +59 -77
  7. package/src/RvfFloatingMenu/RvfFloatingMenu.tsx +1 -1
  8. package/src/RvfIconButton/RvfIconButton.tsx +4 -1
  9. package/src/RvfInputCopy/RvfInputCopy.tsx +57 -0
  10. package/src/RvfInputCopy/index.tsx +1 -0
  11. package/src/RvfLayerTree/TreeItem/TreeItem.tsx +20 -0
  12. package/src/RvfLayerTreeButton/RvfLayerTreeButton.tsx +27 -0
  13. package/src/RvfLayerTreeButton/index.tsx +1 -0
  14. package/src/RvfLineNetworkPlanLayer/RvfLineNetworkPlanLayer.tsx +2 -5
  15. package/src/RvfMobilityMap/RvfMobilityMap.tsx +176 -18
  16. package/src/RvfOverlayHeader/RvfOverlayHeader.tsx +40 -0
  17. package/src/RvfOverlayHeader/index.tsx +1 -0
  18. package/src/RvfPermalink/RvfPermalink.tsx +18 -0
  19. package/src/RvfPermalink/index.tsx +1 -0
  20. package/src/RvfSelect/RvfSelect.tsx +2 -2
  21. package/src/RvfSellingPointsLayer/RvfSellingPointsLayer.tsx +16 -1
  22. package/src/RvfShare/RvfPermalinkButton/RvfPermalinkButton.tsx +61 -0
  23. package/src/RvfShare/RvfPermalinkButton/index.tsx +1 -0
  24. package/src/RvfShare/RvfShare.tsx +40 -0
  25. package/src/RvfShare/index.tsx +1 -0
  26. package/src/RvfShareMenuButton/RvfShareMenuButton.tsx +27 -0
  27. package/src/RvfShareMenuButton/index.tsx +1 -0
  28. package/src/RvfSharedMobilityLayerGroup/RvfSharedMobilityLayerGroup.tsx +64 -15
  29. package/src/RvfTarifZonenLayer/RvfTarifZonenLayer.tsx +2 -5
  30. package/src/icons/Automat/Automat.tsx +8 -0
  31. package/src/icons/Automat/index.tsx +1 -0
  32. package/src/icons/Automat/rvf_automat.svg +15 -0
  33. package/src/icons/Bike/Bike.tsx +8 -0
  34. package/src/icons/Bike/index.tsx +1 -0
  35. package/src/icons/Bike/rvf_shared_bike.svg +15 -0
  36. package/src/icons/Car/Car.tsx +8 -0
  37. package/src/icons/Car/index.tsx +1 -0
  38. package/src/icons/Car/rvf_shared_car.svg +16 -0
  39. package/src/icons/CargoBike/CargoBike.tsx +8 -0
  40. package/src/icons/CargoBike/index.tsx +1 -0
  41. package/src/icons/CargoBike/rvf_shared_cargo_bike.svg +16 -0
  42. package/src/icons/Copy/Copy.tsx +25 -0
  43. package/src/icons/Copy/index.tsx +1 -0
  44. package/src/icons/Doc/Doc.tsx +19 -0
  45. package/src/icons/Doc/doc.svg +7 -0
  46. package/src/icons/Doc/index.tsx +1 -0
  47. package/src/icons/Ebike/Ebike.tsx +8 -0
  48. package/src/icons/Ebike/index.tsx +1 -0
  49. package/src/icons/Ebike/rvf_shared_e-bike.svg +15 -0
  50. package/src/icons/Email/Email.tsx +19 -0
  51. package/src/icons/Email/email.svg +7 -0
  52. package/src/icons/Email/index.tsx +1 -0
  53. package/src/icons/FilePdf/FilePdf.tsx +19 -0
  54. package/src/icons/FilePdf/file-pdf.svg +7 -0
  55. package/src/icons/FilePdf/index.tsx +1 -0
  56. package/src/icons/Image/Image.tsx +24 -0
  57. package/src/icons/Image/index.tsx +1 -0
  58. package/src/icons/InPerson/InPerson.tsx +8 -0
  59. package/src/icons/InPerson/index.tsx +1 -0
  60. package/src/icons/InPerson/rvf_persoenlich.svg +17 -0
  61. package/src/icons/Minus/minus-grey.svg +7 -0
  62. package/src/icons/Ride/Ride.tsx +8 -0
  63. package/src/icons/Ride/index.tsx +1 -0
  64. package/src/icons/Ride/rvf_shared_ride.svg +15 -0
  65. package/src/icons/Scooter/Scooter.tsx +8 -0
  66. package/src/icons/Scooter/index.tsx +1 -0
  67. package/src/icons/Scooter/rvf_shared_scooter.svg +15 -0
  68. package/src/icons/Share/Share.tsx +24 -0
  69. package/src/icons/Share/index.tsx +1 -0
  70. package/src/icons/Stack/Stack.tsx +24 -0
  71. package/src/icons/Stack/index.tsx +1 -0
  72. package/src/icons/Video/Video.tsx +8 -0
  73. package/src/icons/Video/index.tsx +1 -0
  74. package/src/icons/Video/rvf_video.svg +19 -0
  75. package/src/icons/rvf_shared_ride_2.svg +12 -0
  76. package/src/utils/createSharedMobilityLayer.ts +20 -20
  77. package/src/utils/hooks/useRvfContext.tsx +13 -1
  78. package/tailwind.config.mjs +3 -0
  79. package/src/icons/Bicycle/verkehrstraeger-rad-2px-white.svg +0 -19
  80. package/src/icons/Car/verkehrstraeger-auto-2px-white.svg +0 -14
  81. package/src/icons/CargoBicycle/verkehrstraeger-lastenrad-2px-white.svg +0 -27
  82. package/src/icons/Scooter/scooter.svg +0 -10
@@ -33,12 +33,16 @@ import RvfExportMenu from "../RvfExportMenu";
33
33
  import RvfExportMenuButton from "../RvfExportMenuButton";
34
34
  import RvfFeatureDetails from "../RvfFeatureDetails";
35
35
  import RvfFloatingMenu from "../RvfFloatingMenu";
36
+ import RvfLayerTreeButton from "../RvfLayerTreeButton";
36
37
  // Notificationurl example: https://mobility-web-component-tmp.vercel.app/geops-mobility?notificationurl=https%3A%2F%2Fmoco.geops.io%2Fapi%2Fv1%2Fexport%2Fnotification%2F%3Fsso_config%3Dsob&geolocation=false&realtime=false&search=false&notificationat=2024-01-25T22%3A59%3A00Z
37
38
  import RvfLineNetworkPlanLayer from "../RvfLineNetworkPlanLayer";
38
39
  import Modal from "../RvfModal";
40
+ import RvfOverlayHeader from "../RvfOverlayHeader";
39
41
  import RvfPoisLayer from "../RvfPoisLayer";
40
42
  import RvfSellingPointsLayer from "../RvfSellingPointsLayer";
43
+ import RvfShare from "../RvfShare";
41
44
  import RvfSharedMobilityLayerGroup from "../RvfSharedMobilityLayerGroup";
45
+ import RvfShareMenuButton from "../RvfShareMenuButton";
42
46
  import RvfTarifZonenLayer from "../RvfTarifZonenLayer";
43
47
  import Topics from "../RvfTopics";
44
48
  import RvfZoomButtons from "../RvfZoomButtons";
@@ -59,7 +63,7 @@ import MobilityEvent from "../utils/MobilityEvent";
59
63
  // @ts-expect-error bad type definition
60
64
  import style from "./index.css";
61
65
 
62
- export type RvfMobilityMapProps = {} & MobilityMapProps;
66
+ export type RvfMobilityMapProps = { toolbar: string } & MobilityMapProps;
63
67
 
64
68
  const bbox = RVF_EXTENT_3857.join(",");
65
69
 
@@ -98,6 +102,7 @@ function RvfMobilityMap({
98
102
  search = "false",
99
103
  stopsurl = "https://api.geops.io/stops/v1/",
100
104
  tenant = null,
105
+ toolbar = "true",
101
106
  zoom = null,
102
107
  }: RvfMobilityMapProps) {
103
108
  const eventNodeRef = useRef<HTMLDivElement>();
@@ -112,6 +117,7 @@ function RvfMobilityMap({
112
117
  const [stationId, setStationId] = useState<RealtimeStationId>();
113
118
  const [trainId, setTrainId] = useState<RealtimeTrainId>();
114
119
  const [isExportMenuOpen, setIsExportMenuOpen] = useState<boolean>(false);
120
+ const [isShareMenuOpen, setIsShareMenuOpen] = useState<boolean>(false);
115
121
  const [selectedFeature, setSelectedFeature] = useState<Feature>();
116
122
  const [selectedFeatures, setSelectedFeatures] = useState<Feature[]>();
117
123
  const [isLayerTreeOpen, setIsLayerTreeOpen] = useState<boolean>(false);
@@ -123,6 +129,7 @@ function RvfMobilityMap({
123
129
  useState<MaplibreStyleLayer>();
124
130
  const [sharedMobilityLayerGroup, setSharedMobilityLayerGroup] =
125
131
  useState<Group>();
132
+ const [hasToolbar] = useState<boolean>(toolbar === "true");
126
133
 
127
134
  // TODO: this should be removed. The parent application should be responsible to do this
128
135
  // or we should find something that fit more usecases
@@ -223,6 +230,7 @@ function RvfMobilityMap({
223
230
  realtimeurl,
224
231
  search,
225
232
  tenant,
233
+ toolbar,
226
234
  zoom,
227
235
  }),
228
236
  );
@@ -230,6 +238,7 @@ function RvfMobilityMap({
230
238
  baselayer,
231
239
  center,
232
240
  geolocation,
241
+ toolbar,
233
242
  mapsurl,
234
243
  maxzoom,
235
244
  minzoom,
@@ -251,6 +260,7 @@ function RvfMobilityMap({
251
260
  return {
252
261
  isExportMenuOpen,
253
262
  isLayerTreeOpen,
263
+ isShareMenuOpen,
254
264
  lineNetworkPlanLayer,
255
265
  poisLayer,
256
266
  selectedFeature,
@@ -258,6 +268,7 @@ function RvfMobilityMap({
258
268
  sellingPointsLayer,
259
269
  setIsExportMenuOpen,
260
270
  setIsLayerTreeOpen,
271
+ setIsShareMenuOpen,
261
272
  setLineNetworkPlanLayer,
262
273
  setPoisLayer,
263
274
  setSelectedFeature,
@@ -271,6 +282,7 @@ function RvfMobilityMap({
271
282
  }, [
272
283
  isExportMenuOpen,
273
284
  isLayerTreeOpen,
285
+ isShareMenuOpen,
274
286
  lineNetworkPlanLayer,
275
287
  poisLayer,
276
288
  selectedFeature,
@@ -302,6 +314,76 @@ function RvfMobilityMap({
302
314
  };
303
315
  }, []);
304
316
 
317
+ const stationsLayerProps = useMemo(() => {
318
+ return {
319
+ layersFilter: ({ metadata }) => {
320
+ return metadata?.["rvf.filter"] === "netzplan_stations";
321
+ },
322
+ };
323
+ }, []);
324
+
325
+ useEffect(() => {
326
+ if (hasToolbar && isLayerTreeOpen) {
327
+ setIsExportMenuOpen(false);
328
+ setIsLayerTreeOpen(isLayerTreeOpen);
329
+ setSelectedFeature(null);
330
+ setTrainId(null);
331
+ setStationId(null);
332
+ setIsShareMenuOpen(false);
333
+ }
334
+ }, [isLayerTreeOpen, hasToolbar]);
335
+
336
+ useEffect(() => {
337
+ if (hasToolbar && isExportMenuOpen) {
338
+ setIsLayerTreeOpen(false);
339
+ setIsExportMenuOpen(isExportMenuOpen);
340
+ setSelectedFeature(null);
341
+ setTrainId(null);
342
+ setIsShareMenuOpen(false);
343
+ setStationId(null);
344
+ }
345
+ }, [isExportMenuOpen, hasToolbar]);
346
+
347
+ useEffect(() => {
348
+ if (hasToolbar && selectedFeature) {
349
+ setIsLayerTreeOpen(false);
350
+ setIsExportMenuOpen(false);
351
+ setTrainId(null);
352
+ setIsShareMenuOpen(false);
353
+ setStationId(null);
354
+ }
355
+ }, [selectedFeature, hasToolbar]);
356
+
357
+ useEffect(() => {
358
+ if (hasToolbar && stationId) {
359
+ setIsLayerTreeOpen(false);
360
+ setIsExportMenuOpen(false);
361
+ setTrainId(null);
362
+ setIsShareMenuOpen(false);
363
+ setSelectedFeature(null);
364
+ }
365
+ }, [stationId, hasToolbar]);
366
+
367
+ useEffect(() => {
368
+ if (hasToolbar && trainId) {
369
+ setIsLayerTreeOpen(false);
370
+ setIsExportMenuOpen(false);
371
+ setStationId(null);
372
+ setIsShareMenuOpen(false);
373
+ setSelectedFeature(null);
374
+ }
375
+ }, [trainId, hasToolbar]);
376
+
377
+ useEffect(() => {
378
+ if (hasToolbar && isShareMenuOpen) {
379
+ setIsLayerTreeOpen(false);
380
+ setIsExportMenuOpen(false);
381
+ setStationId(null);
382
+ setTrainId(null);
383
+ setSelectedFeature(null);
384
+ }
385
+ }, [isShareMenuOpen, hasToolbar]);
386
+
305
387
  return (
306
388
  <I18nContext.Provider value={i18n}>
307
389
  <style>{tailwind}</style>
@@ -319,11 +401,21 @@ function RvfMobilityMap({
319
401
  <SingleClickListener />
320
402
 
321
403
  {realtime === "true" && <RealtimeLayer title="Echtzeit" />}
322
- {tenant && <StationsLayer />}
404
+ {
405
+ <StationsLayer
406
+ minZoom={10}
407
+ title="Haltestellen"
408
+ {...stationsLayerProps}
409
+ />
410
+ }
323
411
  {notification === "true" && <NotificationLayer />}
324
- <RvfSellingPointsLayer title="Verkaufsstellen" />
325
- <RvfLineNetworkPlanLayer title="Liniennetz" />
326
- <RvfTarifZonenLayer title="Tarifzonen" />
412
+ <RvfSellingPointsLayer isQueryable title="Verkaufsstellen" />
413
+ <RvfLineNetworkPlanLayer
414
+ isQueryable
415
+ minZoom={10}
416
+ title="Liniennetz"
417
+ />
418
+ <RvfTarifZonenLayer isQueryable title="Tarifzonen" />
327
419
  <RvfPoisLayer title="POIs" />
328
420
  <RvfSharedMobilityLayerGroup title="Shared Mobility" />
329
421
 
@@ -337,7 +429,7 @@ function RvfMobilityMap({
337
429
 
338
430
  <div className="absolute right-2 top-2 z-10 flex flex-col gap-2">
339
431
  {geolocation === "true" && <GeolocationButton />}
340
- <RvfExportMenuButton />
432
+ {!hasToolbar && <RvfExportMenuButton />}
341
433
  </div>
342
434
 
343
435
  {search === "true" && (
@@ -350,31 +442,97 @@ function RvfMobilityMap({
350
442
  <RvfZoomButtons />
351
443
  </div>
352
444
 
353
- <RvfFloatingMenu
354
- isOpen={isLayerTreeOpen}
355
- onClick={onLayerTreeMenuClick}
356
- title="Kartendaten"
357
- >
358
- <Topics className={"w-full px-2"} />
359
- </RvfFloatingMenu>
445
+ {!hasToolbar && (
446
+ <RvfFloatingMenu
447
+ isOpen={isLayerTreeOpen}
448
+ onClick={onLayerTreeMenuClick}
449
+ title="Kartendaten"
450
+ >
451
+ <Topics className={"w-full px-2"} />
452
+ </RvfFloatingMenu>
453
+ )}
360
454
  </Map>
361
-
362
455
  <Overlay
363
456
  className={"z-50"}
364
457
  ScrollableHandlerProps={scrollableHandlerProps}
365
458
  >
366
459
  {realtime === "true" && trainId && (
367
- <RouteSchedule className="relative overflow-y-auto overflow-x-hidden" />
460
+ <>
461
+ <RvfOverlayHeader
462
+ onClose={() => {
463
+ setTrainId(null);
464
+ }}
465
+ title="Train"
466
+ ></RvfOverlayHeader>
467
+ <RouteSchedule className="relative overflow-y-auto overflow-x-hidden" />
468
+ </>
368
469
  )}
369
470
  {tenant && stationId && (
370
- <Station className="relative overflow-y-auto overflow-x-hidden" />
471
+ <>
472
+ <RvfOverlayHeader
473
+ onClose={() => {
474
+ setStationId(null);
475
+ }}
476
+ title="Station"
477
+ ></RvfOverlayHeader>
478
+ <Station className="relative flex flex-col overflow-y-auto overflow-x-hidden p-2" />
479
+ </>
371
480
  )}
372
481
  {selectedFeature && (
373
- <RvfFeatureDetails className="relative overflow-y-auto overflow-x-hidden" />
482
+ <>
483
+ <RvfOverlayHeader
484
+ onClose={() => {
485
+ setSelectedFeature(null);
486
+ }}
487
+ title="Informations"
488
+ ></RvfOverlayHeader>
489
+ <RvfFeatureDetails className="relative flex flex-col gap-2 overflow-y-auto overflow-x-hidden p-2" />
490
+ </>
491
+ )}
492
+ {hasToolbar && isExportMenuOpen && (
493
+ <>
494
+ <RvfOverlayHeader
495
+ onClose={() => {
496
+ setIsExportMenuOpen(false);
497
+ }}
498
+ title="Export"
499
+ ></RvfOverlayHeader>
500
+ <RvfExportMenu className="relative flex flex-col gap-2 overflow-y-auto overflow-x-hidden p-2" />
501
+ </>
502
+ )}
503
+ {hasToolbar && isLayerTreeOpen && (
504
+ <>
505
+ <RvfOverlayHeader
506
+ onClose={() => {
507
+ setIsLayerTreeOpen(false);
508
+ }}
509
+ title="Layers"
510
+ ></RvfOverlayHeader>
511
+ <Topics className="relative flex h-full flex-col overflow-y-auto overflow-x-hidden p-2" />
512
+ </>
513
+ )}
514
+ {hasToolbar && isShareMenuOpen && (
515
+ <>
516
+ <RvfOverlayHeader
517
+ onClose={() => {
518
+ setIsLayerTreeOpen(false);
519
+ }}
520
+ title="Share"
521
+ ></RvfOverlayHeader>
522
+ <RvfShare className="relative flex h-full flex-col overflow-y-auto overflow-x-hidden p-2" />
523
+ </>
374
524
  )}
375
525
  </Overlay>
376
526
 
377
- {isExportMenuOpen && (
527
+ {hasToolbar && (
528
+ <div className={"overflow-x-hidden border-r"}>
529
+ <RvfLayerTreeButton className={"border-none"} />
530
+ <RvfExportMenuButton className={"border-none"} />
531
+ <RvfShareMenuButton className={"border-none"} />
532
+ </div>
533
+ )}
534
+
535
+ {!hasToolbar && isExportMenuOpen && (
378
536
  <Modal onClose={onExportMenuClose}>
379
537
  <RvfExportMenu className="relative flex h-full flex-col overflow-y-auto overflow-x-hidden" />
380
538
  </Modal>
@@ -0,0 +1,40 @@
1
+ import type { JSX, PreactDOMAttributes } from "preact";
2
+
3
+ import { memo } from "preact/compat";
4
+ import { twMerge } from "tailwind-merge";
5
+
6
+ import Cancel from "../icons/Cancel";
7
+ import RvfIconButton from "../RvfIconButton";
8
+
9
+ export type RvfOverlayHeaderProps = {
10
+ onClose?: () => void;
11
+ title?: string;
12
+ } & JSX.HTMLAttributes<HTMLDivElement> &
13
+ PreactDOMAttributes;
14
+
15
+ function RvfOverlayHeader({
16
+ children,
17
+ className,
18
+ onClose,
19
+ title,
20
+ ...props
21
+ }: RvfOverlayHeaderProps) {
22
+ return (
23
+ <div
24
+ className={twMerge(
25
+ "flex flex-row items-center justify-between gap-2 p-2 border-b" +
26
+ (className || ""),
27
+ )}
28
+ {...props}
29
+ >
30
+ {children || <span className={"font-bold"}>{title}</span>}
31
+ {onClose && (
32
+ <RvfIconButton className={"!size-[32px] border-none"} onClick={onClose}>
33
+ <Cancel />
34
+ </RvfIconButton>
35
+ )}
36
+ </div>
37
+ );
38
+ }
39
+
40
+ export default memo(RvfOverlayHeader);
@@ -0,0 +1 @@
1
+ export { default } from "./RvfOverlayHeader";
@@ -0,0 +1,18 @@
1
+ import type { JSX, PreactDOMAttributes } from "preact";
2
+
3
+ import RvfInputCopy from "../RvfInputCopy";
4
+
5
+ function RvfPermalink({
6
+ ...props
7
+ }: JSX.HTMLAttributes<HTMLDivElement> & PreactDOMAttributes) {
8
+ return (
9
+ <div {...props}>
10
+ <RvfInputCopy value={window?.location.href} />
11
+ <p className="py-2">
12
+ Sie können auch den Link aus der Adresszeile des Browsers kopieren.
13
+ </p>
14
+ </div>
15
+ );
16
+ }
17
+
18
+ export default RvfPermalink;
@@ -0,0 +1 @@
1
+ export { default } from "./RvfPermalink";
@@ -9,12 +9,12 @@ function RvfSelect({ children, className, onChange }: RvfSelectProps) {
9
9
  return (
10
10
  <div className="relative flex items-center text-grey">
11
11
  <select
12
- className={`min-w-12 appearance-none rounded-xl border border-grey p-2 text-base focus:outline-none ${className}`}
12
+ className={`h-[32px] cursor-pointer appearance-none rounded-[12px] border border-current bg-white px-[13px] text-[16px] leading-[22px] disabled:cursor-default @sm/main:h-[36px] @md/main:h-[40px] ${className}`}
13
13
  onChange={onChange}
14
14
  >
15
15
  {children}
16
16
  </select>
17
- <ArrowDown className="pointer-events-none absolute right-1" />
17
+ <ArrowDown className="pointer-events-none absolute right-[8px]" />
18
18
  </div>
19
19
  );
20
20
  }
@@ -3,10 +3,14 @@ import { MaplibreStyleLayerOptions } from "mobility-toolbox-js/ol/layers/Maplibr
3
3
  import { memo } from "preact/compat";
4
4
  import { useEffect, useMemo } from "preact/hooks";
5
5
 
6
+ import Automat from "../icons/Automat";
7
+ import InPerson from "../icons/InPerson";
8
+ import Video from "../icons/Video";
6
9
  import useMapContext from "../utils/hooks/useMapContext";
7
10
  import useRvfContext from "../utils/hooks/useRvfContext";
8
11
 
9
12
  function RvfSellingPointsLayer(props: MaplibreStyleLayerOptions) {
13
+ const { title } = props;
10
14
  const { baseLayer, map } = useMapContext();
11
15
  const { setSellingPointsLayer } = useRvfContext();
12
16
 
@@ -21,8 +25,19 @@ function RvfSellingPointsLayer(props: MaplibreStyleLayerOptions) {
21
25
  },
22
26
  maplibreLayer: baseLayer,
23
27
  ...(props || {}),
28
+
29
+ title: (
30
+ <div className="flex items-center justify-between gap-2">
31
+ {title}
32
+ <span className="flex items-center">
33
+ <Automat />
34
+ <InPerson />
35
+ <Video />
36
+ </span>
37
+ </div>
38
+ ),
24
39
  });
25
- }, [baseLayer, props]);
40
+ }, [baseLayer, props, title]);
26
41
 
27
42
  useEffect(() => {
28
43
  setSellingPointsLayer(layer);
@@ -0,0 +1,61 @@
1
+ import type { JSX, PreactDOMAttributes } from "preact";
2
+
3
+ import { useState } from "preact/hooks";
4
+
5
+ import Cancel from "../../icons/Cancel";
6
+ import Share from "../../icons/Share";
7
+ import RvfIconButton from "../../RvfIconButton";
8
+ import RvfInputCopy from "../../RvfInputCopy";
9
+
10
+ function RvfPermalinkButton({
11
+ className,
12
+ ...props
13
+ }: JSX.ButtonHTMLAttributes<HTMLButtonElement> & PreactDOMAttributes) {
14
+ const [showTooltip, setShowTooltip] = useState(null);
15
+ const [positionTooltip, setPositionTooltip] = useState<DOMRect>();
16
+
17
+ const handleIconClick = (event) => {
18
+ setPositionTooltip(event.currentTarget.getBoundingClientRect());
19
+ setShowTooltip(!showTooltip);
20
+ };
21
+
22
+ return (
23
+ <>
24
+ <RvfIconButton
25
+ className={"border-none " + className}
26
+ onClick={handleIconClick}
27
+ selected={showTooltip}
28
+ {...props}
29
+ >
30
+ <Share />
31
+ </RvfIconButton>
32
+
33
+ {showTooltip && positionTooltip && (
34
+ <div
35
+ className="fixed rounded-lg border border-grey bg-white p-2 shadow-lg"
36
+ style={{
37
+ left: positionTooltip.left + 40,
38
+ top: positionTooltip.top - 10,
39
+ }}
40
+ >
41
+ <div className="pr-4">
42
+ <RvfIconButton
43
+ className="absolute right-[-5px] top-[-5px] border-none bg-transparent p-0"
44
+ onClick={handleIconClick}
45
+ >
46
+ <Cancel height="18px" width="18px" />
47
+ </RvfIconButton>
48
+ <RvfInputCopy />
49
+ <p className="py-2">
50
+ Sie können auch den Link aus der Adresszeile des Browsers
51
+ kopieren.
52
+ </p>
53
+ </div>
54
+ <div className="absolute -left-1.5 top-4 size-2.5 rotate-45 border border-r-0 border-t-0 border-grey bg-white"></div>
55
+ </div>
56
+ )}
57
+ </>
58
+ );
59
+ }
60
+
61
+ export default RvfPermalinkButton;
@@ -0,0 +1 @@
1
+ export { default } from "./RvfPermalinkButton";
@@ -0,0 +1,40 @@
1
+ import type { JSX, PreactDOMAttributes } from "preact";
2
+
3
+ import CanvasSaveButton from "react-spatial/components/CanvasSaveButton";
4
+ import { twMerge } from "tailwind-merge";
5
+
6
+ import Email from "../icons/Email";
7
+ import Image from "../icons/Image";
8
+ import RvfButton from "../RvfButton";
9
+ import RvfPermalink from "../RvfPermalink";
10
+ import useMapContext from "../utils/hooks/useMapContext";
11
+
12
+ function RvfShare({
13
+ className,
14
+ ...props
15
+ }: JSX.HTMLAttributes<HTMLDivElement> & PreactDOMAttributes) {
16
+ const { map } = useMapContext();
17
+
18
+ return (
19
+ <div className={twMerge("flex flex-col gap-2 " + className)} {...props}>
20
+ <a
21
+ className="flex items-center gap-2"
22
+ href={`mailto:?subject=Karte&body=${window?.location.href}`}
23
+ >
24
+ <RvfButton>
25
+ <Email />
26
+ <span>Email senden</span>
27
+ </RvfButton>
28
+ </a>
29
+ <CanvasSaveButton map={map}>
30
+ <RvfButton className={"w-fit"}>
31
+ <Image />
32
+ <span>Bild speichern</span>
33
+ </RvfButton>
34
+ </CanvasSaveButton>
35
+ <RvfPermalink />
36
+ </div>
37
+ );
38
+ }
39
+
40
+ export default RvfShare;
@@ -0,0 +1 @@
1
+ export { default } from "./RvfShare";
@@ -0,0 +1,27 @@
1
+ import type { JSX, PreactDOMAttributes } from "preact";
2
+
3
+ import { memo } from "preact/compat";
4
+ import { useCallback } from "preact/hooks";
5
+
6
+ import Share from "../icons/Share";
7
+ import RvfIconButton from "../RvfIconButton";
8
+ import useRvfContext from "../utils/hooks/useRvfContext";
9
+
10
+ export type RvfShareMenuButtonProps = JSX.HTMLAttributes<HTMLButtonElement> &
11
+ PreactDOMAttributes;
12
+
13
+ function RvfShareMenuButton({ ...props }: RvfShareMenuButtonProps) {
14
+ const { isShareMenuOpen, setIsShareMenuOpen } = useRvfContext();
15
+
16
+ const onClick = useCallback(() => {
17
+ setIsShareMenuOpen(!isShareMenuOpen);
18
+ }, [isShareMenuOpen, setIsShareMenuOpen]);
19
+
20
+ return (
21
+ <RvfIconButton {...props} onClick={onClick} selected={isShareMenuOpen}>
22
+ <Share />
23
+ </RvfIconButton>
24
+ );
25
+ }
26
+
27
+ export default memo(RvfShareMenuButton);
@@ -0,0 +1 @@
1
+ export { default } from "./RvfShareMenuButton";