@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.
- package/CHANGELOG.md +40 -0
- package/index.js +206 -187
- package/package.json +2 -2
- package/src/Departure/Departure.tsx +6 -3
- package/src/FeatureDetails/FeatureDetails.tsx +4 -4
- package/src/FeaturesInfosListener/FeaturesInfosListener.tsx +22 -0
- package/src/LayerTreeMenu/LayerTreeMenu.tsx +12 -4
- package/src/LayoutState/LayoutState.tsx +144 -63
- package/src/LinesNetworkPlanDetails/LinesNetworkPlanDetails.tsx +2 -2
- package/src/LinesNetworkPlanLayerHighlight/LinesNetworkPlanLayerHighlight.tsx +2 -40
- package/src/MapLayout/MapLayout.tsx +2 -7
- package/src/MobilityMap/MobilityMap.tsx +1 -6
- package/src/MobilityMap/MobilityMapAttributes.ts +28 -7
- package/src/NotificationDetails/NotificationDetails.tsx +21 -22
- package/src/NotificationsLayer/NotificationsLayer.tsx +47 -3
- package/src/OverlayDetails/OverlayDetails.tsx +5 -0
- package/src/RealtimeLayer/RealtimeLayer.tsx +44 -50
- package/src/RouteIcon/RouteIcon.tsx +25 -14
- package/src/RouteSchedule/RouteSchedule.tsx +14 -11
- package/src/RouteScheduleFooter/RouteScheduleFooter.tsx +6 -3
- package/src/RouteScheduleHeader/RouteScheduleHeader.tsx +8 -2
- package/src/RouteStop/RouteStop.tsx +14 -29
- package/src/RouteStopPlatform/RouteStopPlatform.tsx +1 -3
- package/src/RouteStopProgress/RouteStopProgress.tsx +11 -18
- package/src/RouteStopStation/RouteStopStation.tsx +3 -3
- package/src/RvfRealtimeLayer/RvfRealtimeLayer.tsx +16 -3
- package/src/RvfSearch/RvfSearch.tsx +5 -4
- package/src/Search/Search.tsx +41 -20
- package/src/Search/SearchBase.tsx +169 -0
- package/src/Search/SearchHeadless.tsx +79 -0
- package/src/Search/index.tsx +2 -0
- package/src/SearchLinesResult/SearchLinesResult.tsx +43 -0
- package/src/SearchLinesResult/index.tsx +1 -0
- package/src/SearchLinesResults/SearchLinesResults.tsx +106 -0
- package/src/SearchLinesResults/index.tsx +1 -0
- package/src/SearchLnpStopsResult/SearchLnpStopsResult.tsx +42 -0
- package/src/SearchLnpStopsResult/index.tsx +1 -0
- package/src/SearchLnpStopsResults/SearchLnpStopsResults.tsx +106 -0
- package/src/SearchLnpStopsResults/index.tsx +1 -0
- package/src/SearchResult/SearchResult.tsx +25 -0
- package/src/SearchResult/index.tsx +1 -0
- package/src/SearchResults/SearchResults.tsx +81 -0
- package/src/SearchResults/index.tsx +1 -0
- package/src/SearchResultsHeader/SearchResultsHeader.tsx +36 -0
- package/src/SearchResultsHeader/index.tsx +1 -0
- package/src/SearchStopsResult/SearchStopsResult.tsx +43 -0
- package/src/SearchStopsResult/index.tsx +1 -0
- package/src/SearchStopsResults/SearchStopsResults.tsx +101 -0
- package/src/SearchStopsResults/index.tsx +1 -0
- package/src/SearchTrainsResult/SearchTrainsResult.tsx +42 -0
- package/src/SearchTrainsResult/index.tsx +1 -0
- package/src/SearchTrainsResults/SearchTrainsResults.tsx +109 -0
- package/src/SearchTrainsResults/index.tsx +1 -0
- package/src/SingleClickListener/SingleClickListener.tsx +38 -2
- package/src/Station/Station.tsx +23 -48
- package/src/StationHeader/StationHeader.tsx +3 -3
- package/src/StationsLayer/StationsLayer.tsx +9 -1
- package/src/StopsSearch/StopsSearch.tsx +2 -2
- package/src/ui/InputSearch/InputSearch.tsx +105 -0
- package/src/ui/InputSearch/index.tsx +1 -0
- package/src/utils/centerOnVehicle.ts +9 -2
- package/src/utils/constants.ts +8 -1
- package/src/utils/fullTrajectoryStyle.ts +4 -7
- package/src/utils/getBgColor.ts +4 -2
- package/src/utils/getDelayColorForVehicle.test.ts +21 -11
- package/src/utils/getDelayColorForVehicle.ts +7 -5
- package/src/utils/getDelayTextForVehicle.test.ts +12 -12
- package/src/utils/getDelayTextForVehicle.ts +4 -0
- package/src/utils/getMainColorForVehicle.ts +11 -4
- package/src/utils/getRadius.ts +9 -3
- package/src/utils/getTextColor.ts +1 -1
- package/src/utils/getTextColorForVehicle.ts +31 -0
- package/src/utils/getTextFontForVehicle.test.ts +1 -1
- package/src/utils/getTextFontForVehicle.tsx +11 -3
- package/src/utils/getTextForVehicle.ts +7 -1
- package/src/utils/hooks/useFit.tsx +69 -0
- package/src/utils/hooks/useFitOnFeatures.tsx +77 -0
- package/src/utils/hooks/useLayersConfig.tsx +3 -0
- package/src/utils/hooks/useLnp.tsx +39 -5
- package/src/utils/hooks/useMapContext.tsx +2 -5
- package/src/utils/hooks/useRealtimeDepartures.tsx +45 -0
- package/src/utils/hooks/useRealtimeRenderedTrajectory.tsx +42 -0
- package/src/utils/hooks/useRealtimeStation.tsx +39 -0
- package/src/utils/hooks/useRealtimeStopSequences.tsx +43 -0
- package/src/utils/hooks/useRealtimeTrainsByRouteIdentifier.tsx +71 -0
- package/src/utils/hooks/useRouteStop.tsx +7 -1
- package/src/utils/hooks/useSearchLines.tsx +34 -0
- package/src/utils/hooks/useSearchLnpStops.tsx +38 -0
- package/src/utils/hooks/useSearchStops.tsx +85 -0
- package/src/utils/hooks/useSearchTrains.tsx +83 -0
- package/src/utils/realtimeRVFStyle.ts +38 -30
- package/src/utils/translations.ts +17 -0
- package/tash +58 -0
- package/src/utils/centerOnStation.ts +0 -18
- package/src/utils/getDelayFontForVehicle.test.ts +0 -7
- package/src/utils/getDelayFontForVehicle.tsx +0 -8
- 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";
|