@geops/rvf-mobility-web-component 0.1.83 → 0.1.85

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 (97) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/index.js +206 -187
  3. package/package.json +2 -2
  4. package/src/Departure/Departure.tsx +6 -3
  5. package/src/FeatureDetails/FeatureDetails.tsx +4 -4
  6. package/src/FeaturesInfosListener/FeaturesInfosListener.tsx +22 -0
  7. package/src/LayerTreeMenu/LayerTreeMenu.tsx +12 -4
  8. package/src/LayoutState/LayoutState.tsx +144 -63
  9. package/src/LinesNetworkPlanDetails/LinesNetworkPlanDetails.tsx +2 -2
  10. package/src/LinesNetworkPlanLayerHighlight/LinesNetworkPlanLayerHighlight.tsx +2 -40
  11. package/src/MapLayout/MapLayout.tsx +2 -7
  12. package/src/MobilityMap/MobilityMap.tsx +1 -6
  13. package/src/MobilityMap/MobilityMapAttributes.ts +28 -7
  14. package/src/NotificationDetails/NotificationDetails.tsx +21 -22
  15. package/src/NotificationsLayer/NotificationsLayer.tsx +47 -3
  16. package/src/OverlayDetails/OverlayDetails.tsx +5 -0
  17. package/src/RealtimeLayer/RealtimeLayer.tsx +44 -50
  18. package/src/RouteIcon/RouteIcon.tsx +25 -14
  19. package/src/RouteSchedule/RouteSchedule.tsx +14 -11
  20. package/src/RouteScheduleFooter/RouteScheduleFooter.tsx +6 -3
  21. package/src/RouteScheduleHeader/RouteScheduleHeader.tsx +8 -2
  22. package/src/RouteStop/RouteStop.tsx +14 -29
  23. package/src/RouteStopPlatform/RouteStopPlatform.tsx +1 -3
  24. package/src/RouteStopProgress/RouteStopProgress.tsx +11 -18
  25. package/src/RouteStopStation/RouteStopStation.tsx +3 -3
  26. package/src/RvfRealtimeLayer/RvfRealtimeLayer.tsx +16 -3
  27. package/src/RvfSearch/RvfSearch.tsx +5 -4
  28. package/src/Search/Search.tsx +41 -20
  29. package/src/Search/SearchBase.tsx +169 -0
  30. package/src/Search/SearchHeadless.tsx +79 -0
  31. package/src/Search/index.tsx +2 -0
  32. package/src/SearchLinesResult/SearchLinesResult.tsx +43 -0
  33. package/src/SearchLinesResult/index.tsx +1 -0
  34. package/src/SearchLinesResults/SearchLinesResults.tsx +106 -0
  35. package/src/SearchLinesResults/index.tsx +1 -0
  36. package/src/SearchLnpStopsResult/SearchLnpStopsResult.tsx +42 -0
  37. package/src/SearchLnpStopsResult/index.tsx +1 -0
  38. package/src/SearchLnpStopsResults/SearchLnpStopsResults.tsx +106 -0
  39. package/src/SearchLnpStopsResults/index.tsx +1 -0
  40. package/src/SearchResult/SearchResult.tsx +25 -0
  41. package/src/SearchResult/index.tsx +1 -0
  42. package/src/SearchResults/SearchResults.tsx +81 -0
  43. package/src/SearchResults/index.tsx +1 -0
  44. package/src/SearchResultsHeader/SearchResultsHeader.tsx +36 -0
  45. package/src/SearchResultsHeader/index.tsx +1 -0
  46. package/src/SearchStopsResult/SearchStopsResult.tsx +43 -0
  47. package/src/SearchStopsResult/index.tsx +1 -0
  48. package/src/SearchStopsResults/SearchStopsResults.tsx +101 -0
  49. package/src/SearchStopsResults/index.tsx +1 -0
  50. package/src/SearchTrainsResult/SearchTrainsResult.tsx +42 -0
  51. package/src/SearchTrainsResult/index.tsx +1 -0
  52. package/src/SearchTrainsResults/SearchTrainsResults.tsx +109 -0
  53. package/src/SearchTrainsResults/index.tsx +1 -0
  54. package/src/SingleClickListener/SingleClickListener.tsx +38 -2
  55. package/src/Station/Station.tsx +23 -48
  56. package/src/StationHeader/StationHeader.tsx +3 -3
  57. package/src/StationsLayer/StationsLayer.tsx +9 -1
  58. package/src/StopsSearch/StopsSearch.tsx +2 -2
  59. package/src/ui/InputSearch/InputSearch.tsx +105 -0
  60. package/src/ui/InputSearch/index.tsx +1 -0
  61. package/src/utils/centerOnVehicle.ts +9 -2
  62. package/src/utils/constants.ts +8 -1
  63. package/src/utils/fullTrajectoryStyle.ts +4 -7
  64. package/src/utils/getBgColor.ts +4 -2
  65. package/src/utils/getDelayColorForVehicle.test.ts +21 -11
  66. package/src/utils/getDelayColorForVehicle.ts +7 -5
  67. package/src/utils/getDelayTextForVehicle.test.ts +12 -12
  68. package/src/utils/getDelayTextForVehicle.ts +4 -0
  69. package/src/utils/getMainColorForVehicle.ts +11 -4
  70. package/src/utils/getRadius.ts +9 -3
  71. package/src/utils/getTextColor.ts +1 -1
  72. package/src/utils/getTextColorForVehicle.ts +31 -0
  73. package/src/utils/getTextFontForVehicle.test.ts +1 -1
  74. package/src/utils/getTextFontForVehicle.tsx +11 -3
  75. package/src/utils/getTextForVehicle.ts +7 -1
  76. package/src/utils/hooks/useFit.tsx +69 -0
  77. package/src/utils/hooks/useFitOnFeatures.tsx +77 -0
  78. package/src/utils/hooks/useLayersConfig.tsx +3 -0
  79. package/src/utils/hooks/useLnp.tsx +39 -5
  80. package/src/utils/hooks/useMapContext.tsx +2 -5
  81. package/src/utils/hooks/useRealtimeDepartures.tsx +45 -0
  82. package/src/utils/hooks/useRealtimeRenderedTrajectory.tsx +42 -0
  83. package/src/utils/hooks/useRealtimeStation.tsx +39 -0
  84. package/src/utils/hooks/useRealtimeStopSequences.tsx +43 -0
  85. package/src/utils/hooks/useRealtimeTrainsByRouteIdentifier.tsx +71 -0
  86. package/src/utils/hooks/useRouteStop.tsx +7 -1
  87. package/src/utils/hooks/useSearchLines.tsx +34 -0
  88. package/src/utils/hooks/useSearchLnpStops.tsx +38 -0
  89. package/src/utils/hooks/useSearchStops.tsx +85 -0
  90. package/src/utils/hooks/useSearchTrains.tsx +83 -0
  91. package/src/utils/realtimeRVFStyle.ts +38 -30
  92. package/src/utils/translations.ts +17 -0
  93. package/tash +58 -0
  94. package/src/utils/centerOnStation.ts +0 -18
  95. package/src/utils/getDelayFontForVehicle.test.ts +0 -7
  96. package/src/utils/getDelayFontForVehicle.tsx +0 -8
  97. package/src/utils/hooks/useStation.tsx +0 -22
@@ -0,0 +1,106 @@
1
+ import { cloneElement, toChildArray } from "preact";
2
+ import { memo } from "preact/compat";
3
+ import { useCallback, useContext, useMemo } from "preact/hooks";
4
+
5
+ import { SearchContext } from "../Search/SearchBase";
6
+ import SearchResult from "../SearchResult";
7
+ import SearchResults from "../SearchResults";
8
+ import SearchResultsHeader from "../SearchResultsHeader";
9
+ import useI18n from "../utils/hooks/useI18n";
10
+ import useSearchLines from "../utils/hooks/useSearchLines";
11
+
12
+ import type { ReactElement } from "preact/compat";
13
+
14
+ import type {
15
+ SearchResultsChildProps,
16
+ SearchResultsProps,
17
+ } from "../SearchResults/SearchResults";
18
+ import type { LnpLineInfo } from "../utils/hooks/useLnp";
19
+
20
+ const defaultSort = (a: LnpLineInfo, b: LnpLineInfo) => {
21
+ if (a.long_name === b.long_name) {
22
+ return a.long_name < b.long_name ? 1 : -1;
23
+ }
24
+ return a.short_name < b.short_name ? 1 : -1;
25
+ };
26
+
27
+ function SearchLinesResults({
28
+ children,
29
+ filter,
30
+ resultClassName,
31
+ resultsClassName,
32
+ resultsContainerClassName,
33
+ sort = defaultSort,
34
+ }: SearchResultsProps<LnpLineInfo>) {
35
+ const { open, query, setOpen, setSelectedQuery } = useContext(SearchContext);
36
+ const searchResponse = useSearchLines(query);
37
+
38
+ const { t } = useI18n();
39
+
40
+ const onSelectResult = useCallback(
41
+ (item: LnpLineInfo) => {
42
+ setSelectedQuery(item.short_name || item.long_name);
43
+ setOpen(false);
44
+ },
45
+ [setOpen, setSelectedQuery],
46
+ );
47
+
48
+ const results = useMemo(() => {
49
+ let rs = [...(searchResponse?.results || [])];
50
+
51
+ if (filter) {
52
+ rs = rs.filter(filter);
53
+ }
54
+
55
+ if (sort) {
56
+ rs = rs.sort(sort);
57
+ }
58
+ return rs;
59
+ }, [searchResponse, filter, sort]);
60
+
61
+ const searchResponseFiltered = useMemo(() => {
62
+ return {
63
+ ...searchResponse,
64
+ results,
65
+ };
66
+ }, [results, searchResponse]);
67
+
68
+ const showResults = useMemo(() => {
69
+ return open && !!results?.length;
70
+ }, [open, results]);
71
+
72
+ if (!showResults) {
73
+ return null;
74
+ }
75
+
76
+ return (
77
+ <>
78
+ <SearchResultsHeader>{t("search_lines_results")}</SearchResultsHeader>
79
+ <SearchResults
80
+ className={resultsContainerClassName}
81
+ resultsClassName={resultsClassName}
82
+ searchResponse={searchResponseFiltered}
83
+ >
84
+ {results.map((item: LnpLineInfo) => {
85
+ return (
86
+ <SearchResult className={resultClassName} key={item.short_name}>
87
+ {toChildArray(children).map(
88
+ (child: ReactElement<SearchResultsChildProps<LnpLineInfo>>) => {
89
+ const onSelectItem = (itemm: LnpLineInfo, evt: Event) => {
90
+ onSelectResult(itemm);
91
+ child.props?.onSelectItem?.(itemm, evt);
92
+ };
93
+ return cloneElement(child, {
94
+ item: item,
95
+ onSelectItem,
96
+ });
97
+ },
98
+ )}
99
+ </SearchResult>
100
+ );
101
+ })}
102
+ </SearchResults>
103
+ </>
104
+ );
105
+ }
106
+ export default memo(SearchLinesResults);
@@ -0,0 +1 @@
1
+ export { default } from "./SearchLinesResults";
@@ -0,0 +1,42 @@
1
+ import { memo } from "preact/compat";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ import useMapContext from "../utils/hooks/useMapContext";
5
+
6
+ import type { ButtonHTMLAttributes, PreactDOMAttributes } from "preact";
7
+
8
+ import type { LnpStopInfo } from "../utils/hooks/useLnp";
9
+
10
+ export type SearchLinesResultProps = {
11
+ className?: string;
12
+ item?: LnpStopInfo;
13
+ onSelectItem?: (line: LnpStopInfo) => void;
14
+ } & ButtonHTMLAttributes<HTMLButtonElement> &
15
+ PreactDOMAttributes;
16
+
17
+ function SearchLnpStopsResult({
18
+ className,
19
+ item,
20
+ onSelectItem,
21
+ ...props
22
+ }: SearchLinesResultProps) {
23
+ const { linesNetworkPlanLayer } = useMapContext();
24
+ return (
25
+ <button
26
+ {...props}
27
+ className={twMerge(
28
+ "flex w-full cursor-pointer items-center gap-3 text-left",
29
+ className,
30
+ )}
31
+ onClick={() => {
32
+ linesNetworkPlanLayer?.setVisible(true);
33
+ onSelectItem?.(item);
34
+ }}
35
+ >
36
+ <div className="size-6"></div>
37
+ <div className="grow">{item?.long_name || item?.short_name}</div>
38
+ </button>
39
+ );
40
+ }
41
+
42
+ export default memo(SearchLnpStopsResult);
@@ -0,0 +1 @@
1
+ export { default } from "./SearchLnpStopsResult";
@@ -0,0 +1,106 @@
1
+ import { cloneElement, toChildArray } from "preact";
2
+ import { memo } from "preact/compat";
3
+ import { useCallback, useContext, useMemo } from "preact/hooks";
4
+
5
+ import { SearchContext } from "../Search/SearchBase";
6
+ import SearchResult from "../SearchResult";
7
+ import SearchResults from "../SearchResults";
8
+ import SearchResultsHeader from "../SearchResultsHeader";
9
+ import useI18n from "../utils/hooks/useI18n";
10
+ import useSearchLnpStops from "../utils/hooks/useSearchLnpStops";
11
+
12
+ import type { ReactElement } from "preact/compat";
13
+
14
+ import type {
15
+ SearchResultsChildProps,
16
+ SearchResultsProps,
17
+ } from "../SearchResults/SearchResults";
18
+ import type { LnpStopInfo } from "../utils/hooks/useLnp";
19
+
20
+ const defaultSort = (a: LnpStopInfo, b: LnpStopInfo) => {
21
+ if (a.long_name === b.long_name) {
22
+ return a.long_name < b.long_name ? 1 : -1;
23
+ }
24
+ return a.short_name < b.short_name ? 1 : -1;
25
+ };
26
+
27
+ function SearchLnpStopsResults({
28
+ children,
29
+ filter,
30
+ resultClassName,
31
+ resultsClassName,
32
+ resultsContainerClassName,
33
+ sort = defaultSort,
34
+ }: SearchResultsProps<LnpStopInfo>) {
35
+ const { open, query, setOpen, setSelectedQuery } = useContext(SearchContext);
36
+ const searchResponse = useSearchLnpStops(query);
37
+
38
+ const { t } = useI18n();
39
+
40
+ const onSelectResult = useCallback(
41
+ (item: LnpStopInfo) => {
42
+ setSelectedQuery(item.short_name || item.long_name);
43
+ setOpen(false);
44
+ },
45
+ [setOpen, setSelectedQuery],
46
+ );
47
+
48
+ const results = useMemo(() => {
49
+ let rs = [...(searchResponse?.results || [])];
50
+
51
+ if (filter) {
52
+ rs = rs.filter(filter);
53
+ }
54
+
55
+ if (sort) {
56
+ rs = rs.sort(sort);
57
+ }
58
+ return rs;
59
+ }, [searchResponse, filter, sort]);
60
+
61
+ const searchResponseFiltered = useMemo(() => {
62
+ return {
63
+ ...searchResponse,
64
+ results,
65
+ };
66
+ }, [results, searchResponse]);
67
+
68
+ const showResults = useMemo(() => {
69
+ return open && !!results?.length;
70
+ }, [open, results]);
71
+
72
+ if (!showResults) {
73
+ return null;
74
+ }
75
+
76
+ return (
77
+ <>
78
+ <SearchResultsHeader>{t("search_stops_results")}</SearchResultsHeader>
79
+ <SearchResults
80
+ className={resultsContainerClassName}
81
+ resultsClassName={resultsClassName}
82
+ searchResponse={searchResponseFiltered}
83
+ >
84
+ {results.map((item: LnpStopInfo) => {
85
+ return (
86
+ <SearchResult className={resultClassName} key={item.short_name}>
87
+ {toChildArray(children).map(
88
+ (child: ReactElement<SearchResultsChildProps<LnpStopInfo>>) => {
89
+ const onSelectItem = (itemm: LnpStopInfo, evt: Event) => {
90
+ onSelectResult(itemm);
91
+ child.props?.onSelectItem?.(itemm, evt);
92
+ };
93
+ return cloneElement(child, {
94
+ item: item,
95
+ onSelectItem,
96
+ });
97
+ },
98
+ )}
99
+ </SearchResult>
100
+ );
101
+ })}
102
+ </SearchResults>
103
+ </>
104
+ );
105
+ }
106
+ export default memo(SearchLnpStopsResults);
@@ -0,0 +1 @@
1
+ export { default } from "./SearchLnpStopsResults";
@@ -0,0 +1,25 @@
1
+ import { memo } from "preact/compat";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ import type { HTMLAttributes, PreactDOMAttributes } from "preact";
5
+
6
+ export type SearchResultProps = {
7
+ className?: string;
8
+ } & HTMLAttributes<HTMLLIElement> &
9
+ PreactDOMAttributes;
10
+
11
+ function SearchResult({ children, className, ...props }: SearchResultProps) {
12
+ return (
13
+ <li
14
+ {...props}
15
+ className={twMerge(
16
+ "border-b border-dashed border-slate-300 p-3 last:border-0",
17
+ className,
18
+ )}
19
+ >
20
+ {children}
21
+ </li>
22
+ );
23
+ }
24
+
25
+ export default memo(SearchResult);
@@ -0,0 +1 @@
1
+ export { default } from "./SearchResult";
@@ -0,0 +1,81 @@
1
+ import { memo, useContext, useEffect, useId } from "preact/compat";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ import { SearchContext } from "../Search/SearchBase";
5
+ import useI18n from "../utils/hooks/useI18n";
6
+
7
+ import type { HTMLAttributes, PreactDOMAttributes } from "preact";
8
+
9
+ import type { SearchResponse } from "../utils/hooks/useSearchStops";
10
+
11
+ export type SearchResultsProps<T> = {
12
+ className?: string;
13
+ filter?: (item: T) => boolean;
14
+ resultClassName?: string;
15
+ resultsClassName?: string;
16
+ resultsContainerClassName?: string;
17
+ searchResponse?: SearchResponse<T>;
18
+ sort?: (a: T, b: T) => number;
19
+ } & HTMLAttributes<HTMLDivElement> &
20
+ PreactDOMAttributes;
21
+
22
+ export interface SearchResultsChildProps<T> {
23
+ item: T;
24
+ onSelectItem?: (item: T, evt: Event) => void;
25
+ }
26
+
27
+ /**
28
+ * Results list of search.
29
+ */
30
+ function SearchResults<T>({
31
+ children,
32
+ className,
33
+ resultsClassName,
34
+ searchResponse,
35
+ ...props
36
+ }: SearchResultsProps<T>) {
37
+ const { t } = useI18n();
38
+ const id = useId();
39
+ const { setResults } = useContext(SearchContext);
40
+
41
+ // Notify parent about results change
42
+ useEffect(() => {
43
+ if (!searchResponse.isLoading) {
44
+ setResults(id, searchResponse?.results || []);
45
+ }
46
+ return () => {
47
+ setResults(id, []);
48
+ };
49
+ }, [searchResponse, id, setResults]);
50
+
51
+ if (!(searchResponse?.results?.length >= 0)) {
52
+ return null;
53
+ }
54
+
55
+ return (
56
+ <div className={twMerge("flex grow overflow-hidden", className)} {...props}>
57
+ {searchResponse.results.length === 0 && (
58
+ <div
59
+ className={twMerge(
60
+ "flex grow gap-3 border border-solid p-3 pt-2 text-zinc-400",
61
+ resultsClassName,
62
+ )}
63
+ style={{ border: 1 }}
64
+ >
65
+ <div className="size-6"></div>
66
+ <div>{t("search_no_results")}</div>
67
+ </div>
68
+ )}
69
+ {searchResponse.results.length > 0 && (
70
+ <ul
71
+ className={twMerge("grow overflow-y-auto", resultsClassName)}
72
+ style={{ border: 1 }} // without this th ul is displayed 1 px on the right
73
+ >
74
+ {children}
75
+ </ul>
76
+ )}
77
+ </div>
78
+ );
79
+ }
80
+
81
+ export default memo(SearchResults);
@@ -0,0 +1 @@
1
+ export { default } from "./SearchResults";
@@ -0,0 +1,36 @@
1
+ import { memo } from "preact/compat";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ import type { HTMLAttributes, PreactDOMAttributes } from "preact";
5
+
6
+ export type SearchResultsHeaderProps = {
7
+ className?: string;
8
+ resultsClassName?: string;
9
+ } & HTMLAttributes<HTMLDivElement> &
10
+ PreactDOMAttributes;
11
+
12
+ /**
13
+ * Header of list of search results.
14
+ */
15
+ function SearchResultsHeader({
16
+ children,
17
+ className,
18
+ ...props
19
+ }: SearchResultsHeaderProps) {
20
+ if (!children) {
21
+ return null;
22
+ }
23
+ return (
24
+ <div
25
+ {...props}
26
+ className={twMerge(
27
+ "border-x bg-zinc-100 px-2 py-1 text-xs font-semibold",
28
+ className,
29
+ )}
30
+ >
31
+ {children}
32
+ </div>
33
+ );
34
+ }
35
+
36
+ export default memo(SearchResultsHeader);
@@ -0,0 +1 @@
1
+ export { default } from "./SearchResultsHeader";
@@ -0,0 +1,43 @@
1
+ import { memo } from "preact/compat";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ import useMapContext from "../utils/hooks/useMapContext";
5
+
6
+ import type { ButtonHTMLAttributes, PreactDOMAttributes } from "preact";
7
+
8
+ import type { StopsFeature } from "../utils/hooks/useSearchStops";
9
+
10
+ export type SearchStopsResultProps = {
11
+ className?: string;
12
+ item?: StopsFeature;
13
+ onSelectItem?: (stop: StopsFeature, evt: MouseEvent) => void;
14
+ } & ButtonHTMLAttributes<HTMLButtonElement> &
15
+ PreactDOMAttributes;
16
+
17
+ function SearchStopsResult({
18
+ className,
19
+ item,
20
+ onSelectItem,
21
+ ...props
22
+ }: SearchStopsResultProps) {
23
+ const { stationsLayer } = useMapContext();
24
+
25
+ return (
26
+ <button
27
+ className={twMerge(
28
+ "flex w-full cursor-pointer items-center gap-3 text-left",
29
+ className,
30
+ )}
31
+ onClick={(evt) => {
32
+ stationsLayer?.setVisible(true);
33
+ onSelectItem?.(item, evt);
34
+ }}
35
+ {...props}
36
+ >
37
+ <div className="size-6"></div>
38
+ <div className="grow">{item?.properties.name}</div>
39
+ </button>
40
+ );
41
+ }
42
+
43
+ export default memo(SearchStopsResult);
@@ -0,0 +1 @@
1
+ export { default } from "./SearchStopsResult";
@@ -0,0 +1,101 @@
1
+ import { cloneElement, toChildArray } from "preact";
2
+ import { memo } from "preact/compat";
3
+ import { useCallback, useContext, useMemo } from "preact/hooks";
4
+
5
+ import { SearchContext } from "../Search/SearchBase";
6
+ import SearchResult from "../SearchResult";
7
+ import SearchResults from "../SearchResults";
8
+ import SearchResultsHeader from "../SearchResultsHeader";
9
+ import useI18n from "../utils/hooks/useI18n";
10
+ import useSearchStops from "../utils/hooks/useSearchStops";
11
+
12
+ import type { ReactElement } from "preact/compat";
13
+
14
+ import type {
15
+ SearchResultsChildProps,
16
+ SearchResultsProps,
17
+ } from "../SearchResults/SearchResults";
18
+ import type { StopsFeature } from "../utils/hooks/useSearchStops";
19
+
20
+ function SearchStopsResults({
21
+ children,
22
+ filter,
23
+ resultClassName,
24
+ resultsClassName,
25
+ resultsContainerClassName,
26
+ sort,
27
+ }: SearchResultsProps<StopsFeature>) {
28
+ const { open, query, setOpen, setSelectedQuery } = useContext(SearchContext);
29
+ const searchResponse = useSearchStops(query);
30
+
31
+ const { t } = useI18n();
32
+
33
+ const onSelectResult = useCallback(
34
+ (item: StopsFeature) => {
35
+ setSelectedQuery(item.properties.name);
36
+ setOpen(false);
37
+ },
38
+ [setOpen, setSelectedQuery],
39
+ );
40
+
41
+ const results = useMemo(() => {
42
+ let rs = [...(searchResponse?.results || [])];
43
+
44
+ if (filter) {
45
+ rs = rs.filter(filter);
46
+ }
47
+
48
+ if (sort) {
49
+ rs = rs.sort(sort);
50
+ }
51
+ return rs;
52
+ }, [searchResponse, filter, sort]);
53
+
54
+ const searchResponseFiltered = useMemo(() => {
55
+ return {
56
+ ...searchResponse,
57
+ results,
58
+ };
59
+ }, [results, searchResponse]);
60
+
61
+ const showResults = useMemo(() => {
62
+ return open && !!results?.length;
63
+ }, [open, results]);
64
+
65
+ if (!showResults) {
66
+ return null;
67
+ }
68
+
69
+ return (
70
+ <>
71
+ <SearchResultsHeader>{t("search_stops_results")}</SearchResultsHeader>
72
+ <SearchResults
73
+ className={resultsContainerClassName}
74
+ resultsClassName={resultsClassName}
75
+ searchResponse={searchResponseFiltered}
76
+ >
77
+ {results.map((item: StopsFeature) => {
78
+ return (
79
+ <SearchResult className={resultClassName} key={item.properties.uid}>
80
+ {toChildArray(children).map(
81
+ (
82
+ child: ReactElement<SearchResultsChildProps<StopsFeature>>,
83
+ ) => {
84
+ const onSelectItem = (itemm: StopsFeature, evt: Event) => {
85
+ onSelectResult(itemm);
86
+ child.props?.onSelectItem?.(itemm, evt);
87
+ };
88
+ return cloneElement(child, {
89
+ item: item,
90
+ onSelectItem,
91
+ });
92
+ },
93
+ )}
94
+ </SearchResult>
95
+ );
96
+ })}
97
+ </SearchResults>
98
+ </>
99
+ );
100
+ }
101
+ export default memo(SearchStopsResults);
@@ -0,0 +1 @@
1
+ export { default } from "./SearchStopsResults";
@@ -0,0 +1,42 @@
1
+ import { memo } from "preact/compat";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ import RouteIcon from "../RouteIcon";
5
+ import useMapContext from "../utils/hooks/useMapContext";
6
+
7
+ import type { RealtimeRouteIdentifierMatch } from "mobility-toolbox-js/types";
8
+ import type { ButtonHTMLAttributes, PreactDOMAttributes } from "preact";
9
+
10
+ export type SearchTrainsResultProps = {
11
+ className?: string;
12
+ item?: RealtimeRouteIdentifierMatch;
13
+ onSelectItem?: (item: RealtimeRouteIdentifierMatch, evt: MouseEvent) => void;
14
+ } & ButtonHTMLAttributes<HTMLButtonElement> &
15
+ PreactDOMAttributes;
16
+
17
+ function SearchTrainsResult({
18
+ className,
19
+ item,
20
+ onSelectItem,
21
+ ...props
22
+ }: SearchTrainsResultProps) {
23
+ const { realtimeLayer } = useMapContext();
24
+ return (
25
+ <button
26
+ {...props}
27
+ className={twMerge(
28
+ "flex w-full cursor-pointer items-center gap-3 text-left",
29
+ className,
30
+ )}
31
+ onClick={(evt) => {
32
+ realtimeLayer?.setVisible(true);
33
+ onSelectItem?.(item, evt);
34
+ }}
35
+ >
36
+ <RouteIcon line={item.trains[0].line}></RouteIcon>
37
+ <div className="grow">{`${item.trains[0].line?.name} (${item.route_identifier})`}</div>
38
+ </button>
39
+ );
40
+ }
41
+
42
+ export default memo(SearchTrainsResult);
@@ -0,0 +1 @@
1
+ export { default } from "./SearchTrainsResult";