@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
package/package.json CHANGED
@@ -2,21 +2,23 @@
2
2
  "name": "@geops/rvf-mobility-web-component",
3
3
  "license": "UNLICENSED",
4
4
  "description": "Web components for rvf in the domains of mobility and logistics.",
5
- "version": "0.1.13",
5
+ "version": "0.1.14",
6
6
  "homepage": "https://rvf-mobility-web-component-geops.vercel.app/",
7
7
  "type": "module",
8
8
  "main": "index.js",
9
9
  "dependencies": {
10
10
  "jspdf": "^2.5.2",
11
11
  "maplibre-gl": "^4.7.1",
12
- "mobility-toolbox-js": "3.1.0",
12
+ "mobility-toolbox-js": "3.1.1-beta.0",
13
13
  "ol": "^10.3.1",
14
14
  "preact": "^10.25.4",
15
15
  "preact-custom-element": "^4.3.0",
16
16
  "react": "npm:@preact/compat@^18.3.1",
17
17
  "react-dom": "npm:@preact/compat@^18.3.1",
18
18
  "react-icons": "^5.4.0",
19
- "rosetta": "^1.1.0"
19
+ "react-spatial": "^1.12.2",
20
+ "rosetta": "^1.1.0",
21
+ "tailwind-merge": "^2.6.0"
20
22
  },
21
23
  "devDependencies": {
22
24
  "@commitlint/cli": "^19.6.1",
@@ -1,6 +1,7 @@
1
1
  import type { JSX, PreactDOMAttributes } from "preact";
2
2
 
3
3
  import { memo, useMemo } from "preact/compat";
4
+ import { twMerge } from "tailwind-merge";
4
5
 
5
6
  export type RvfButtonProps = {
6
7
  selected?: boolean;
@@ -9,7 +10,7 @@ export type RvfButtonProps = {
9
10
  PreactDOMAttributes;
10
11
 
11
12
  const baseClasses =
12
- "flex text-[14px] @sm/main:text-[16px] @md/main:text-[18px] h-[32px] @sm/main:h-[36px] @md/main:h-[40px] px-[22px] @sm/main:px-[22.5px] @md/main:px-[27.5px] pt-[7px] pb-[5px] @sm/main:pt-[8px] @sm/main:pb-[6px] @md/main:pt-[8.5px] @md/main:pb-[6.5px] items-center justify-center rounded-full border font-semibold text-button";
13
+ "flex gap-2 text-[14px] @sm/main:text-[16px] @md/main:text-[18px] h-[32px] @sm/main:h-[36px] @md/main:h-[40px] px-[22px] @sm/main:px-[22.5px] @md/main:px-[27.5px] pt-[7px] pb-[5px] @sm/main:pt-[8px] @sm/main:pb-[6px] @md/main:pt-[8.5px] @md/main:pb-[6.5px] items-center justify-left rounded-full border font-semibold text-button";
13
14
 
14
15
  export const themes = {
15
16
  primary: {
@@ -32,7 +33,9 @@ function RvfButton({
32
33
  ...props
33
34
  }: RvfButtonProps) {
34
35
  const classes = useMemo(() => {
35
- return `${baseClasses} ${themes[theme].classes} ${selected ? themes[theme].selectedClasses : ""} ${className || ""}`;
36
+ return twMerge(
37
+ `${baseClasses} ${themes[theme].classes} ${selected ? themes[theme].selectedClasses : ""} ${className || ""}`,
38
+ );
36
39
  }, [className, selected, theme]);
37
40
 
38
41
  return (
@@ -1,20 +1,26 @@
1
1
  import type { JSX } from "preact";
2
2
 
3
3
  import { memo } from "preact/compat";
4
+ import { twMerge } from "tailwind-merge";
4
5
 
5
6
  // @ts-expect-error - required for htm to resolve the JSX pragma
6
7
  import ok from "../icons/Ok/ok-grey.svg";
7
8
 
8
- export type RvfCheckboxProps = {} & JSX.InputHTMLAttributes<HTMLInputElement>;
9
+ export type RvfCheckboxProps = {
10
+ checkedIconUrl?: string;
11
+ } & JSX.InputHTMLAttributes<HTMLInputElement>;
9
12
 
10
13
  function RvfCheckbox({ className, ...props }: RvfCheckboxProps) {
14
+ const checkedIconUrl = props.checkedIconUrl || ok;
11
15
  return (
12
16
  <input
13
- className={`
14
- box-border size-[20px] flex-none cursor-pointer appearance-none rounded border-2 border-grey bg-white bg-contain bg-center text-grey disabled:cursor-default disabled:border-lightgrey ${className}`}
17
+ className={twMerge(`
18
+ box-border size-[20px] flex-none cursor-pointer appearance-none rounded border-2 border-grey bg-white bg-contain bg-center text-grey disabled:cursor-default disabled:border-lightgrey ${className}`)}
15
19
  style={{
16
20
  backgroundImage:
17
- props.checked && !props.disabled ? `url('` + ok + `')` : "",
21
+ props.checked && !props.disabled
22
+ ? `url('` + checkedIconUrl + `')`
23
+ : "",
18
24
  }}
19
25
  {...props}
20
26
  type="checkbox"
@@ -2,16 +2,13 @@ import type { JSX, PreactDOMAttributes } from "preact";
2
2
 
3
3
  import { memo, useId, useState } from "preact/compat";
4
4
 
5
- import Cancel from "../icons/Cancel";
6
5
  import RvfButton from "../RvfButton";
7
6
  import RvfCheckbox from "../RvfCheckbox";
8
- import RvfIconButton from "../RvfIconButton";
9
7
  import RvfSelect from "../RvfSelect";
10
8
  import { LAYER_PROP_IS_EXPORTING } from "../utils/constants";
11
9
  import exportPdf from "../utils/exportPdf";
12
10
  import getAllLayers from "../utils/getAllLayers";
13
11
  import useMapContext from "../utils/hooks/useMapContext";
14
- import useRvfContext from "../utils/hooks/useRvfContext";
15
12
 
16
13
  export type RvfExportMenuButtonProps = JSX.HTMLAttributes<HTMLDivElement> &
17
14
  PreactDOMAttributes;
@@ -19,7 +16,6 @@ export type RvfExportMenuButtonProps = JSX.HTMLAttributes<HTMLDivElement> &
19
16
  const formats = ["A4", "A3", "A1", "A0"];
20
17
 
21
18
  function RvfExportMenu({ ...props }: RvfExportMenuButtonProps) {
22
- const { setIsExportMenuOpen } = useRvfContext();
23
19
  const { map } = useMapContext();
24
20
  const [useMaxExtent, setUseMaxExtent] = useState(false);
25
21
  const [format, setFormat] = useState<string>(formats[0]);
@@ -29,85 +25,71 @@ function RvfExportMenu({ ...props }: RvfExportMenuButtonProps) {
29
25
  const [isExportingError, setIsExportingError] = useState(false);
30
26
  return (
31
27
  <div {...props}>
32
- <div className={"flex h-full flex-col gap-2 p-2"}>
33
- {/* <!-- Header --> */}
34
- <div className={"flex flex-row items-center justify-between gap-2"}>
35
- <span className={"font-bold"}>Drucken </span>
36
- <RvfIconButton
37
- className={"!size-[32px]"}
38
- onClick={() => {
39
- setIsExportMenuOpen(false);
28
+ {/* <!-- Content --> */}
29
+ <div className="flex flex-1 flex-col gap-2">
30
+ <div className={"flex flex-wrap items-center gap-2"}>
31
+ <RvfCheckbox
32
+ checked={useMaxExtent}
33
+ id={checkboxId}
34
+ onChange={() => {
35
+ return setUseMaxExtent(!useMaxExtent);
40
36
  }}
41
- >
42
- <Cancel />
43
- </RvfIconButton>
44
- </div>
45
- {/* <!-- Content --> */}
46
- <div className="flex flex-1 flex-col gap-2">
47
- <div className={"flex flex-wrap items-center gap-2"}>
48
- <RvfCheckbox
49
- checked={useMaxExtent}
50
- id={checkboxId}
51
- onChange={() => {
52
- return setUseMaxExtent(!useMaxExtent);
53
- }}
54
- />
55
- <label htmlFor={checkboxId}>Ganze Region exportieren</label>
56
- </div>
57
- <div className={"flex flex-wrap items-center gap-2"}>
58
- <label htmlFor={selectId}>Format:</label>
59
- <RvfSelect
60
- className={"w-24"}
61
- id={selectId}
62
- onChange={(evt) => {
63
- setFormat((evt.target as HTMLSelectElement).value);
64
- }}
65
- >
66
- {formats.map((format) => {
67
- return <option key={format}>{format}</option>;
68
- })}
69
- </RvfSelect>
70
- </div>
37
+ />
38
+ <label htmlFor={checkboxId}>Ganze Region exportieren</label>
71
39
  </div>
72
- {/* <!-- Footer --> */}
73
- <div>
74
- <RvfButton
75
- disabled={isExporting}
76
- onClick={async () => {
77
- setIsExportingError(false);
78
- setIsExporting(true);
79
- const result = await exportPdf(
80
- map,
81
- { format },
82
- {
83
- onAfter: (map, layers) => {
84
- getAllLayers(layers).forEach((layer) => {
85
- layer.set(LAYER_PROP_IS_EXPORTING, false);
86
- });
87
- },
88
-
89
- onBefore: (map, layers) => {
90
- getAllLayers(layers).forEach((layer) => {
91
- layer.set(LAYER_PROP_IS_EXPORTING, true);
92
- });
93
- },
94
- useMaxExtent,
95
- },
96
- );
97
- setTimeout(() => {
98
- setIsExporting(false);
99
- setIsExportingError(!result);
100
- }, 1000);
40
+ <div className={"flex flex-wrap items-center gap-2"}>
41
+ <label htmlFor={selectId}>Format:</label>
42
+ <RvfSelect
43
+ className={"w-24"}
44
+ id={selectId}
45
+ onChange={(evt) => {
46
+ setFormat((evt.target as HTMLSelectElement).value);
101
47
  }}
102
48
  >
103
- {isExporting
104
- ? "Exporting..."
105
- : isExportingError
106
- ? "Error"
107
- : "Download"}
108
- </RvfButton>
49
+ {formats.map((format) => {
50
+ return <option key={format}>{format}</option>;
51
+ })}
52
+ </RvfSelect>
109
53
  </div>
110
54
  </div>
55
+ {/* <!-- Footer --> */}
56
+ <div>
57
+ <RvfButton
58
+ disabled={isExporting}
59
+ onClick={async () => {
60
+ setIsExportingError(false);
61
+ setIsExporting(true);
62
+ const result = await exportPdf(
63
+ map,
64
+ { format },
65
+ {
66
+ onAfter: (map, layers) => {
67
+ getAllLayers(layers).forEach((layer) => {
68
+ layer.set(LAYER_PROP_IS_EXPORTING, false);
69
+ });
70
+ },
71
+
72
+ onBefore: (map, layers) => {
73
+ getAllLayers(layers).forEach((layer) => {
74
+ layer.set(LAYER_PROP_IS_EXPORTING, true);
75
+ });
76
+ },
77
+ useMaxExtent,
78
+ },
79
+ );
80
+ setTimeout(() => {
81
+ setIsExporting(false);
82
+ setIsExportingError(!result);
83
+ }, 1000);
84
+ }}
85
+ >
86
+ {isExporting
87
+ ? "Exporting..."
88
+ : isExportingError
89
+ ? "Error"
90
+ : "Download"}
91
+ </RvfButton>
92
+ </div>
111
93
  </div>
112
94
  );
113
95
  }
@@ -32,7 +32,7 @@ function RvfFloatingMenu({
32
32
  {title} {isOpen ? <ArrowDown /> : <ArrowUp />}
33
33
  </button>
34
34
  {!isOpen && (
35
- <div className="flex h-[calc(100%-39px)] w-full flex-1 overflow-y-auto">
35
+ <div className="flex h-[calc(100%-39px)] w-full flex-1 flex-col overflow-y-auto">
36
36
  {children}
37
37
  </div>
38
38
  )}
@@ -1,6 +1,7 @@
1
1
  import type { JSX, PreactDOMAttributes } from "preact";
2
2
 
3
3
  import { memo, SVGProps, useMemo } from "preact/compat";
4
+ import { twMerge } from "tailwind-merge";
4
5
 
5
6
  import { RvfButtonProps, themes } from "../RvfButton/RvfButton";
6
7
 
@@ -22,7 +23,9 @@ function RvfIconButton({
22
23
  ...props
23
24
  }: RvfIconButtonProps) {
24
25
  const classes = useMemo(() => {
25
- return `${baseClasses} ${themes[theme].classes} ${selected ? themes[theme].selectedClasses : ""} ${className || ""}`;
26
+ return twMerge(
27
+ `${baseClasses} ${themes[theme].classes} ${selected ? themes[theme].selectedClasses : ""} ${className || ""}`,
28
+ );
26
29
  }, [className, selected, theme]);
27
30
 
28
31
  return (
@@ -0,0 +1,57 @@
1
+ import type { JSX, PreactDOMAttributes } from "preact";
2
+
3
+ import { useRef, useState } from "preact/hooks";
4
+
5
+ import Copy from "../icons/Copy";
6
+ import RvfIconButton from "../RvfIconButton";
7
+
8
+ function RvfInputCopy(props: JSX.InputHTMLAttributes & PreactDOMAttributes) {
9
+ const [positionTooltip, setPositionTooltip] = useState<DOMRect>();
10
+ const [isTooptipShowed, setIsTooltipShowed] = useState(false);
11
+ const inputRef = useRef(null);
12
+
13
+ const handleCopyClick = (event) => {
14
+ setPositionTooltip(event.currentTarget.getBoundingClientRect());
15
+ navigator.clipboard.writeText(window?.location.href).then(() => {
16
+ setIsTooltipShowed(true);
17
+ setTimeout(() => {
18
+ setIsTooltipShowed(false);
19
+ }, 1000);
20
+ });
21
+ inputRef.current.select();
22
+ };
23
+
24
+ const handleInputFocus = () => {
25
+ inputRef.current.select();
26
+ };
27
+
28
+ return (
29
+ <div className="flex items-center text-grey">
30
+ <input
31
+ className="h-7 w-full rounded-sm rounded-r-none border border-r-0 border-current p-1 leading-4 outline-none"
32
+ onFocus={handleInputFocus}
33
+ readOnly
34
+ ref={inputRef}
35
+ type="text"
36
+ {...props}
37
+ />
38
+ <RvfIconButton
39
+ className="!size-7 rounded-l-none rounded-r-sm border-current"
40
+ onClick={handleCopyClick}
41
+ >
42
+ <Copy />
43
+ </RvfIconButton>
44
+ <div
45
+ className={`fixed rounded-md bg-grey p-1 text-sm text-white ${isTooptipShowed ? "block" : "hidden"}`}
46
+ style={{
47
+ left: positionTooltip?.left - 30,
48
+ top: positionTooltip?.top - 40,
49
+ }}
50
+ >
51
+ Link kopiert!
52
+ </div>
53
+ </div>
54
+ );
55
+ }
56
+
57
+ export default RvfInputCopy;
@@ -0,0 +1 @@
1
+ export { default } from "./RvfInputCopy";
@@ -11,6 +11,7 @@ import {
11
11
 
12
12
  import ArrowDown from "../../icons/ArrowDown";
13
13
  import ArrowUp from "../../icons/ArrowUp";
14
+ import minusGrey from "../../icons/Minus/minus-grey.svg";
14
15
  import RvfCheckbox from "../../RvfCheckbox";
15
16
  import RvfRadioButton from "../../RvfRadioButton";
16
17
  import { LayersTreeDispatchContext } from "../layersTreeContext";
@@ -88,6 +89,23 @@ function TreeItem({
88
89
  return null;
89
90
  }
90
91
 
92
+ const isMiddleState = () => {
93
+ if (childItems.length > 0) {
94
+ const checkedItems = childItems.filter((item) => {
95
+ return item.isControlChecked;
96
+ });
97
+ if (checkedItems.length === childItems.length) {
98
+ return false;
99
+ }
100
+
101
+ return childItems.some((item) => {
102
+ return item.isControlChecked;
103
+ });
104
+ }
105
+
106
+ return false;
107
+ };
108
+
91
109
  return (
92
110
  <div>
93
111
  <div className="flex items-center gap-2 border-b py-2 pr-1">
@@ -100,6 +118,8 @@ function TreeItem({
100
118
  ) : (
101
119
  <RvfCheckbox
102
120
  checked={isControlChecked}
121
+ checkedIconUrl={isMiddleState() ? minusGrey : null}
122
+ className={isMiddleState() ? "bg-[length:18px]" : ""}
103
123
  id={inputId}
104
124
  onChange={handleSelectionChange}
105
125
  />
@@ -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 Stack from "../icons/Stack";
7
+ import RvfIconButton from "../RvfIconButton";
8
+ import useRvfContext from "../utils/hooks/useRvfContext";
9
+
10
+ export type RvfLayerTreeButtonProps = JSX.HTMLAttributes<HTMLButtonElement> &
11
+ PreactDOMAttributes;
12
+
13
+ function RvfLayerTreeButton({ ...props }: RvfLayerTreeButtonProps) {
14
+ const { isLayerTreeOpen, setIsLayerTreeOpen } = useRvfContext();
15
+
16
+ const onClick = useCallback(() => {
17
+ setIsLayerTreeOpen(!isLayerTreeOpen);
18
+ }, [isLayerTreeOpen, setIsLayerTreeOpen]);
19
+
20
+ return (
21
+ <RvfIconButton {...props} onClick={onClick} selected={isLayerTreeOpen}>
22
+ <Stack />
23
+ </RvfIconButton>
24
+ );
25
+ }
26
+
27
+ export default memo(RvfLayerTreeButton);
@@ -0,0 +1 @@
1
+ export { default } from "./RvfLayerTreeButton";
@@ -16,11 +16,8 @@ function RvfLineNetworkPlanLayer(props: MaplibreStyleLayerOptions) {
16
16
  }
17
17
  return new MaplibreStyleLayer({
18
18
  isQueryable: true,
19
- layersFilter: ({ metadata, source }) => {
20
- return (
21
- metadata?.["rvf.filter"] === "netowrk_plans" ||
22
- source === "network_plans"
23
- );
19
+ layersFilter: ({ metadata }) => {
20
+ return metadata?.["rvf.filter"] === "netzplan_lines";
24
21
  },
25
22
  maplibreLayer: baseLayer,
26
23
  minZoom: 10,