@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
|
@@ -6,8 +6,14 @@ import RouteInfos from "../RouteInfos";
|
|
|
6
6
|
import IconButton from "../ui/IconButton";
|
|
7
7
|
import useMapContext from "../utils/hooks/useMapContext";
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
import type { RealtimeStopSequence } from "mobility-toolbox-js/types";
|
|
10
|
+
|
|
11
|
+
function RouteScheduleHeader({
|
|
12
|
+
stopSequence,
|
|
13
|
+
}: {
|
|
14
|
+
stopSequence: RealtimeStopSequence;
|
|
15
|
+
}) {
|
|
16
|
+
const { isFollowing, setIsFollowing } = useMapContext();
|
|
11
17
|
return (
|
|
12
18
|
<div className="flex items-center gap-x-4 bg-slate-100 p-4 py-2">
|
|
13
19
|
<RouteIcon stopSequence={stopSequence} />
|
|
@@ -8,10 +8,14 @@ import RouteStopStation from "../RouteStopStation";
|
|
|
8
8
|
import RouteStopTime from "../RouteStopTime";
|
|
9
9
|
import getStopStatus from "../utils/getStopStatus";
|
|
10
10
|
import useMapContext from "../utils/hooks/useMapContext";
|
|
11
|
+
import useRealtimeStation from "../utils/hooks/useRealtimeStation";
|
|
11
12
|
import { RouteStopContext } from "../utils/hooks/useRouteStop";
|
|
12
13
|
|
|
13
|
-
import type {
|
|
14
|
-
|
|
14
|
+
import type {
|
|
15
|
+
RealtimeStop,
|
|
16
|
+
RealtimeStopSequence,
|
|
17
|
+
} from "mobility-toolbox-js/types";
|
|
18
|
+
import type { HTMLAttributes, PreactDOMAttributes } from "preact";
|
|
15
19
|
|
|
16
20
|
export type RouteScheduleStopProps = {
|
|
17
21
|
classNameGreyOut?: string;
|
|
@@ -20,7 +24,8 @@ export type RouteScheduleStopProps = {
|
|
|
20
24
|
stop?: {
|
|
21
25
|
platform?: string;
|
|
22
26
|
} & RealtimeStop;
|
|
23
|
-
|
|
27
|
+
stopSequence: RealtimeStopSequence;
|
|
28
|
+
} & HTMLAttributes<HTMLButtonElement> &
|
|
24
29
|
PreactDOMAttributes;
|
|
25
30
|
|
|
26
31
|
function RouteStop({
|
|
@@ -29,15 +34,13 @@ function RouteStop({
|
|
|
29
34
|
index,
|
|
30
35
|
invertColor = false,
|
|
31
36
|
stop,
|
|
37
|
+
stopSequence,
|
|
32
38
|
...props
|
|
33
39
|
}: RouteScheduleStopProps) {
|
|
34
|
-
const { map
|
|
35
|
-
const {
|
|
36
|
-
// @ts-expect-error bad type definition
|
|
37
|
-
stopUID,
|
|
38
|
-
} = stop;
|
|
39
|
-
const [station, setStation] = useState<RealtimeStation>();
|
|
40
|
+
const { map } = useMapContext();
|
|
41
|
+
const { stopUID } = stop;
|
|
40
42
|
const [status, setStatus] = useState(getStopStatus(stopSequence, index));
|
|
43
|
+
const station = useRealtimeStation(stopUID);
|
|
41
44
|
|
|
42
45
|
useEffect(() => {
|
|
43
46
|
let interval = null;
|
|
@@ -54,27 +57,9 @@ function RouteStop({
|
|
|
54
57
|
};
|
|
55
58
|
}, [index, status.isInBetween, stopSequence]);
|
|
56
59
|
|
|
57
|
-
useEffect(() => {
|
|
58
|
-
if (!stopUID || !realtimeLayer?.api) {
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
realtimeLayer?.api?.subscribe(`station ${stopUID}`, ({ content }) => {
|
|
62
|
-
if (content) {
|
|
63
|
-
setStation(content);
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
return () => {
|
|
68
|
-
setStation(null);
|
|
69
|
-
if (stopUID) {
|
|
70
|
-
realtimeLayer?.api?.unsubscribe(`station ${stopUID}`);
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
}, [stopUID, realtimeLayer?.api]);
|
|
74
|
-
|
|
75
60
|
const routeStopState = useMemo(() => {
|
|
76
|
-
return { index, invertColor, station, status, stop };
|
|
77
|
-
}, [stop, status, index, invertColor, station]);
|
|
61
|
+
return { index, invertColor, station, status, stop, stopSequence };
|
|
62
|
+
}, [stop, status, index, invertColor, station, stopSequence]);
|
|
78
63
|
|
|
79
64
|
let colorScheme = status.isPassed || status.isLeft ? classNameGreyOut : "";
|
|
80
65
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { memo } from "preact/compat";
|
|
2
2
|
|
|
3
3
|
import useI18n from "../utils/hooks/useI18n";
|
|
4
|
-
import useMapContext from "../utils/hooks/useMapContext";
|
|
5
4
|
import useRouteStop from "../utils/hooks/useRouteStop";
|
|
6
5
|
|
|
7
6
|
import type { JSX, PreactDOMAttributes } from "preact";
|
|
@@ -10,8 +9,7 @@ export type RouteStopPlatformProps = JSX.HTMLAttributes<HTMLSpanElement> &
|
|
|
10
9
|
PreactDOMAttributes;
|
|
11
10
|
|
|
12
11
|
function RouteStopPlatform({ ...props }: RouteStopPlatformProps) {
|
|
13
|
-
const { stop } = useRouteStop();
|
|
14
|
-
const { stopSequence } = useMapContext();
|
|
12
|
+
const { stop, stopSequence } = useRouteStop();
|
|
15
13
|
const { type } = stopSequence;
|
|
16
14
|
const { t } = useI18n();
|
|
17
15
|
const { platform } = stop || {};
|
|
@@ -1,40 +1,33 @@
|
|
|
1
1
|
import { memo } from "preact/compat";
|
|
2
2
|
|
|
3
3
|
import getMainColorForVehicle from "../utils/getMainColorForVehicle";
|
|
4
|
-
import useMapContext from "../utils/hooks/useMapContext";
|
|
5
4
|
import useRouteStop from "../utils/hooks/useRouteStop";
|
|
6
5
|
|
|
7
|
-
import type {
|
|
6
|
+
import type { HTMLAttributes, PreactDOMAttributes } from "preact";
|
|
8
7
|
|
|
9
8
|
export type RouteStopProgressProps = {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
} & JSX.HTMLAttributes<HTMLDivElement> &
|
|
9
|
+
svgProps?: HTMLAttributes<SVGElement> & PreactDOMAttributes;
|
|
10
|
+
} & HTMLAttributes<HTMLDivElement> &
|
|
13
11
|
PreactDOMAttributes;
|
|
14
12
|
|
|
15
|
-
function RouteStopProgress({
|
|
16
|
-
|
|
17
|
-
svgProps,
|
|
18
|
-
...props
|
|
19
|
-
}: RouteStopProgressProps) {
|
|
20
|
-
const { stopSequence } = useMapContext();
|
|
21
|
-
const { invertColor, status } = useRouteStop();
|
|
13
|
+
function RouteStopProgress({ svgProps, ...props }: RouteStopProgressProps) {
|
|
14
|
+
const { invertColor, status, stopSequence } = useRouteStop();
|
|
22
15
|
const { isBoarding, isFirst, isLast, isLeft, isPassed, progress } = status;
|
|
23
16
|
const y1 = isFirst ? "50%" : "-100%";
|
|
24
17
|
const y2 = isLast ? "50%" : "100%";
|
|
25
18
|
const yDone = `${progress}%`;
|
|
26
19
|
|
|
27
20
|
const greyColor = "rgb(156, 163, 175)";
|
|
28
|
-
const
|
|
21
|
+
const lineColor = getMainColorForVehicle(stopSequence);
|
|
29
22
|
|
|
30
|
-
let colorScheme = isPassed ? greyColor :
|
|
31
|
-
let invertColorScheme = isPassed ?
|
|
23
|
+
let colorScheme = isPassed ? greyColor : lineColor;
|
|
24
|
+
let invertColorScheme = isPassed ? lineColor : greyColor;
|
|
32
25
|
let progressDoneColor = greyColor;
|
|
33
26
|
|
|
34
27
|
if (invertColor) {
|
|
35
|
-
colorScheme = isPassed ?
|
|
36
|
-
invertColorScheme = isPassed ? greyColor :
|
|
37
|
-
progressDoneColor =
|
|
28
|
+
colorScheme = isPassed ? lineColor : greyColor;
|
|
29
|
+
invertColorScheme = isPassed ? greyColor : lineColor;
|
|
30
|
+
progressDoneColor = lineColor;
|
|
38
31
|
}
|
|
39
32
|
|
|
40
33
|
const circleColor =
|
|
@@ -15,7 +15,7 @@ export type RouteStopStationProps = {
|
|
|
15
15
|
|
|
16
16
|
function RouteStopStation({
|
|
17
17
|
children,
|
|
18
|
-
className = "flex items-center gap-2",
|
|
18
|
+
className = "flex items-center gap-2 w-full",
|
|
19
19
|
classNameCancelled = "text-red-600 line-through",
|
|
20
20
|
...props
|
|
21
21
|
}: RouteStopStationProps) {
|
|
@@ -29,8 +29,8 @@ function RouteStopStation({
|
|
|
29
29
|
status.isCancelled ? classNameCancelled : "",
|
|
30
30
|
)}
|
|
31
31
|
>
|
|
32
|
-
<RouteStopName />
|
|
33
|
-
<RouteStopServices className="flex flex-wrap gap-1" />
|
|
32
|
+
<RouteStopName className={"grow"} />
|
|
33
|
+
<RouteStopServices className="flex shrink-0 flex-wrap gap-1" />
|
|
34
34
|
{children}
|
|
35
35
|
</div>
|
|
36
36
|
);
|
|
@@ -8,6 +8,7 @@ import getTextColor from "../utils/getTextColor";
|
|
|
8
8
|
import realtimeRVFStyle from "../utils/realtimeRVFStyle";
|
|
9
9
|
|
|
10
10
|
import type { RealtimeLayerOptions } from "mobility-toolbox-js/ol";
|
|
11
|
+
import type { RealtimeTrajectory, ViewState } from "mobility-toolbox-js/types";
|
|
11
12
|
|
|
12
13
|
const PRIORITY_FROM_TYPE = {
|
|
13
14
|
bus: 25,
|
|
@@ -46,15 +47,27 @@ const defaultProps: Partial<RealtimeLayerOptions> = {
|
|
|
46
47
|
},
|
|
47
48
|
style: realtimeRVFStyle,
|
|
48
49
|
styleOptions: {
|
|
49
|
-
|
|
50
|
+
getColor: (trajectory?: RealtimeTrajectory): string => {
|
|
51
|
+
return getBgColor(
|
|
52
|
+
trajectory?.properties?.type,
|
|
53
|
+
trajectory?.properties?.line,
|
|
54
|
+
);
|
|
55
|
+
},
|
|
50
56
|
getMaxRadiusForStrokeAndDelay: () => {
|
|
51
57
|
return 4;
|
|
52
58
|
},
|
|
53
59
|
getMaxRadiusForText: () => {
|
|
54
60
|
return 8;
|
|
55
61
|
},
|
|
56
|
-
getRadius:
|
|
57
|
-
|
|
62
|
+
getRadius: (
|
|
63
|
+
trajectory?: RealtimeTrajectory,
|
|
64
|
+
viewState?: ViewState,
|
|
65
|
+
): number => {
|
|
66
|
+
return getRadius(trajectory?.properties?.type, viewState?.zoom);
|
|
67
|
+
},
|
|
68
|
+
getTextColor: (trajectory?: RealtimeTrajectory): string => {
|
|
69
|
+
return getTextColor(trajectory?.properties?.type);
|
|
70
|
+
},
|
|
58
71
|
},
|
|
59
72
|
};
|
|
60
73
|
function RvfRealtimeLayer(props: Partial<RealtimeLayerOptions>) {
|
|
@@ -2,20 +2,21 @@ import { memo } from "preact/compat";
|
|
|
2
2
|
import { useCallback } from "preact/hooks";
|
|
3
3
|
|
|
4
4
|
import StopsSearch from "../StopsSearch";
|
|
5
|
-
import centerOnStation from "../utils/centerOnStation";
|
|
6
5
|
import { RVF_EXTENT_4326 } from "../utils/constants";
|
|
6
|
+
import useFit from "../utils/hooks/useFit";
|
|
7
7
|
import useMapContext from "../utils/hooks/useMapContext";
|
|
8
8
|
|
|
9
9
|
import type { StopsSearchProps } from "../StopsSearch/StopsSearch";
|
|
10
10
|
|
|
11
11
|
function RvfSearch(props: Partial<StopsSearchProps>) {
|
|
12
|
-
const { apikey,
|
|
12
|
+
const { apikey, stopsurl } = useMapContext();
|
|
13
|
+
const fit = useFit();
|
|
13
14
|
|
|
14
15
|
const onSelect = useCallback(
|
|
15
16
|
(selected) => {
|
|
16
|
-
return
|
|
17
|
+
return fit.current(selected);
|
|
17
18
|
},
|
|
18
|
-
[
|
|
19
|
+
[fit],
|
|
19
20
|
);
|
|
20
21
|
|
|
21
22
|
return (
|
package/src/Search/Search.tsx
CHANGED
|
@@ -1,29 +1,50 @@
|
|
|
1
1
|
import { memo } from "preact/compat";
|
|
2
|
-
import {
|
|
2
|
+
import { twMerge } from "tailwind-merge";
|
|
3
3
|
|
|
4
|
-
import
|
|
5
|
-
import centerOnStation from "../utils/centerOnStation";
|
|
6
|
-
import useMapContext from "../utils/hooks/useMapContext";
|
|
4
|
+
import { SearchHeadless } from ".";
|
|
7
5
|
|
|
8
|
-
import type {
|
|
9
|
-
|
|
10
|
-
function Search(props: Partial<StopsSearchProps>) {
|
|
11
|
-
const { apikey, map, stopsurl } = useMapContext();
|
|
12
|
-
|
|
13
|
-
const onSelect = useCallback(
|
|
14
|
-
(selected) => {
|
|
15
|
-
return centerOnStation(selected, map);
|
|
16
|
-
},
|
|
17
|
-
[map],
|
|
18
|
-
);
|
|
6
|
+
import type { SearchProps } from "./SearchHeadless";
|
|
19
7
|
|
|
8
|
+
/**
|
|
9
|
+
* This compoennt only define defult classNames for the Search component.
|
|
10
|
+
*
|
|
11
|
+
* Since the Search component could be used in different places (with or without toolbar),
|
|
12
|
+
* but the logic behind stays the same, we separate the logic and the styles in 2 components.
|
|
13
|
+
*/
|
|
14
|
+
function Search({
|
|
15
|
+
childrenContainerClassName,
|
|
16
|
+
className,
|
|
17
|
+
inputContainerClassName,
|
|
18
|
+
resultClassName,
|
|
19
|
+
resultsContainerClassName,
|
|
20
|
+
withResultsClassName,
|
|
21
|
+
...props
|
|
22
|
+
}: SearchProps) {
|
|
20
23
|
return (
|
|
21
|
-
<
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
<SearchHeadless
|
|
25
|
+
childrenContainerClassName={twMerge(
|
|
26
|
+
"max-h-[300px] rounded-b-2xl bg-white shadow overflow-hidden",
|
|
27
|
+
childrenContainerClassName,
|
|
28
|
+
)}
|
|
29
|
+
className={twMerge(
|
|
30
|
+
"border-grey @container m-0 h-[48px] gap-2 rounded-2xl border p-2 text-base",
|
|
31
|
+
className,
|
|
32
|
+
)}
|
|
33
|
+
inputContainerClassName={twMerge("border-none", inputContainerClassName)}
|
|
34
|
+
resultClassName={twMerge(
|
|
35
|
+
"text-base **:hover:cursor-pointer p-2",
|
|
36
|
+
resultClassName,
|
|
37
|
+
)}
|
|
38
|
+
resultsContainerClassName={twMerge(
|
|
39
|
+
"min-h-[100px] max-h-[200px] border border-t-0",
|
|
40
|
+
resultsContainerClassName,
|
|
41
|
+
)}
|
|
42
|
+
withResultsClassName={twMerge(
|
|
43
|
+
"text-base !rounded-b-none",
|
|
44
|
+
withResultsClassName,
|
|
45
|
+
)}
|
|
25
46
|
{...props}
|
|
26
|
-
|
|
47
|
+
></SearchHeadless>
|
|
27
48
|
);
|
|
28
49
|
}
|
|
29
50
|
export default memo(Search);
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { memo } from "preact/compat";
|
|
2
|
+
import { useCallback, useMemo, useRef, useState } from "preact/hooks";
|
|
3
|
+
|
|
4
|
+
import InputSearch from "../ui/InputSearch";
|
|
5
|
+
import useI18n from "../utils/hooks/useI18n";
|
|
6
|
+
|
|
7
|
+
import type { TargetedInputEvent } from "preact";
|
|
8
|
+
import type { ReactElement } from "preact/compat";
|
|
9
|
+
|
|
10
|
+
import type { IconButtonProps } from "../ui/IconButton/IconButton";
|
|
11
|
+
import type { InputProps } from "../ui/Input/Input";
|
|
12
|
+
import type { InputSearchProps } from "../ui/InputSearch/InputSearch";
|
|
13
|
+
|
|
14
|
+
export type SearchBaseProps = {
|
|
15
|
+
cancelButtonProps?: IconButtonProps;
|
|
16
|
+
childrenContainerClassName?: string;
|
|
17
|
+
inputProps?: InputProps;
|
|
18
|
+
withResultsClassName?: string;
|
|
19
|
+
} & InputSearchProps &
|
|
20
|
+
Pick<
|
|
21
|
+
Partial<SearchResultsProps<unknown>>,
|
|
22
|
+
"resultClassName" | "resultsClassName" | "resultsContainerClassName"
|
|
23
|
+
>;
|
|
24
|
+
|
|
25
|
+
import { cloneElement, createContext, toChildArray } from "preact";
|
|
26
|
+
import { twMerge } from "tailwind-merge";
|
|
27
|
+
|
|
28
|
+
import type { SearchResultsProps } from "../SearchResults/SearchResults";
|
|
29
|
+
|
|
30
|
+
export interface SearchContextType {
|
|
31
|
+
open: boolean;
|
|
32
|
+
query: string;
|
|
33
|
+
selectedQuery: string;
|
|
34
|
+
setOpen: (open: boolean) => void;
|
|
35
|
+
setQuery: (query: string) => void;
|
|
36
|
+
setResults: (id: string, results: unknown[]) => void;
|
|
37
|
+
setSelectedQuery: (query: string) => void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const SearchContext = createContext<null | SearchContextType>({
|
|
41
|
+
open: false,
|
|
42
|
+
query: "",
|
|
43
|
+
selectedQuery: "",
|
|
44
|
+
setOpen: () => {},
|
|
45
|
+
setQuery: () => {},
|
|
46
|
+
setResults: () => {},
|
|
47
|
+
setSelectedQuery: () => {},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
function SearchBase({
|
|
51
|
+
cancelButtonProps,
|
|
52
|
+
children,
|
|
53
|
+
childrenContainerClassName,
|
|
54
|
+
inputProps,
|
|
55
|
+
resultClassName,
|
|
56
|
+
resultsClassName,
|
|
57
|
+
resultsContainerClassName,
|
|
58
|
+
withResultsClassName,
|
|
59
|
+
...props
|
|
60
|
+
}: SearchBaseProps) {
|
|
61
|
+
const { t } = useI18n();
|
|
62
|
+
// We use a selectedQuery state to avoid making a new request when we select a result.
|
|
63
|
+
const [selectedQuery, setSelectedQuery] = useState("");
|
|
64
|
+
const [query, setQuery] = useState("");
|
|
65
|
+
const [showResults, setShowResults] = useState(false);
|
|
66
|
+
const [open, setOpen] = useState(false);
|
|
67
|
+
const resultsBySearchRef = useRef<Record<string, unknown[]>>({});
|
|
68
|
+
|
|
69
|
+
const setResults = useCallback(
|
|
70
|
+
(id: string, results: unknown[]) => {
|
|
71
|
+
resultsBySearchRef.current[id] = results;
|
|
72
|
+
setShowResults(
|
|
73
|
+
open &&
|
|
74
|
+
Object.values(resultsBySearchRef.current).some((r) => {
|
|
75
|
+
return r.length > 0;
|
|
76
|
+
}),
|
|
77
|
+
);
|
|
78
|
+
},
|
|
79
|
+
[open],
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const value = useMemo(() => {
|
|
83
|
+
return {
|
|
84
|
+
open,
|
|
85
|
+
query,
|
|
86
|
+
selectedQuery,
|
|
87
|
+
setOpen,
|
|
88
|
+
setQuery,
|
|
89
|
+
setResults: setResults,
|
|
90
|
+
setSelectedQuery,
|
|
91
|
+
};
|
|
92
|
+
}, [open, query, selectedQuery, setResults]);
|
|
93
|
+
|
|
94
|
+
const inputPropss: InputProps = useMemo(() => {
|
|
95
|
+
return {
|
|
96
|
+
placeholder: t("search_placeholder"),
|
|
97
|
+
...(inputProps || {}),
|
|
98
|
+
onChange: (evt: TargetedInputEvent<HTMLInputElement>) => {
|
|
99
|
+
setQuery((evt.target as HTMLInputElement).value);
|
|
100
|
+
setSelectedQuery("");
|
|
101
|
+
setOpen(true);
|
|
102
|
+
},
|
|
103
|
+
onFocus: () => {
|
|
104
|
+
setOpen(true);
|
|
105
|
+
},
|
|
106
|
+
value: selectedQuery || query,
|
|
107
|
+
};
|
|
108
|
+
}, [query, selectedQuery, t, inputProps]);
|
|
109
|
+
|
|
110
|
+
const cancelButtonPropss: IconButtonProps = useMemo(() => {
|
|
111
|
+
return {
|
|
112
|
+
...(cancelButtonProps || {}),
|
|
113
|
+
onClick: () => {
|
|
114
|
+
setSelectedQuery("");
|
|
115
|
+
setQuery("");
|
|
116
|
+
setOpen(false);
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}, [cancelButtonProps]);
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<SearchContext.Provider value={value}>
|
|
123
|
+
<InputSearch
|
|
124
|
+
{...props}
|
|
125
|
+
cancelButtonProps={cancelButtonPropss}
|
|
126
|
+
inputProps={inputPropss}
|
|
127
|
+
withResultsClassName={showResults ? withResultsClassName : ""}
|
|
128
|
+
>
|
|
129
|
+
<div className={twMerge("flex flex-col", childrenContainerClassName)}>
|
|
130
|
+
{toChildArray(children)?.map((child: ReactElement) => {
|
|
131
|
+
return cloneElement(child, {
|
|
132
|
+
resultClassName,
|
|
133
|
+
resultsClassName,
|
|
134
|
+
resultsContainerClassName,
|
|
135
|
+
});
|
|
136
|
+
})}
|
|
137
|
+
{/*} {showTrajectoriesResults && (
|
|
138
|
+
<>
|
|
139
|
+
<SearchResultsHeader>
|
|
140
|
+
{t("search_trajectories_results")}
|
|
141
|
+
</SearchResultsHeader>
|
|
142
|
+
<SearchResults
|
|
143
|
+
className={resultsContainerClassName}
|
|
144
|
+
resultsClassName={resultsClassName}
|
|
145
|
+
resultsContainerClassName={"grow"}
|
|
146
|
+
searchResponse={trajectories}
|
|
147
|
+
>
|
|
148
|
+
{trajectories.results.map((trajectory: RealtimeTrajectory) => {
|
|
149
|
+
return (
|
|
150
|
+
<SearchResult
|
|
151
|
+
className={resultClassName}
|
|
152
|
+
key={trajectory.properties.route_identifier}
|
|
153
|
+
>
|
|
154
|
+
<SearchTrajectoriesResult
|
|
155
|
+
onSelect={onSelectTrajectory}
|
|
156
|
+
trajectory={trajectory}
|
|
157
|
+
/>
|
|
158
|
+
</SearchResult>
|
|
159
|
+
);
|
|
160
|
+
})}
|
|
161
|
+
</SearchResults>
|
|
162
|
+
</>
|
|
163
|
+
)} */}
|
|
164
|
+
</div>
|
|
165
|
+
</InputSearch>
|
|
166
|
+
</SearchContext.Provider>
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
export default memo(SearchBase);
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { memo, useCallback } from "preact/compat";
|
|
2
|
+
|
|
3
|
+
import SearchLinesResult from "../SearchLinesResult";
|
|
4
|
+
import SearchLinesResults from "../SearchLinesResults";
|
|
5
|
+
import SearchStopsResult from "../SearchStopsResult";
|
|
6
|
+
import SearchStopsResults from "../SearchStopsResults";
|
|
7
|
+
import SearchTrainsResult from "../SearchTrainsResult";
|
|
8
|
+
import SearchTrainsResults from "../SearchTrainsResults";
|
|
9
|
+
import useFit from "../utils/hooks/useFit";
|
|
10
|
+
import useMapContext from "../utils/hooks/useMapContext";
|
|
11
|
+
|
|
12
|
+
import { SearchBase } from ".";
|
|
13
|
+
|
|
14
|
+
import type { RealtimeRouteIdentifierMatch } from "mobility-toolbox-js/types";
|
|
15
|
+
|
|
16
|
+
import type { IconButtonProps } from "../ui/IconButton/IconButton";
|
|
17
|
+
import type { InputProps } from "../ui/Input/Input";
|
|
18
|
+
import type { LnpLineInfo } from "../utils/hooks/useLnp";
|
|
19
|
+
import type { StopsFeature } from "../utils/hooks/useSearchStops";
|
|
20
|
+
|
|
21
|
+
import type { SearchBaseProps } from "./SearchBase";
|
|
22
|
+
|
|
23
|
+
export type SearchProps = {
|
|
24
|
+
cancelButtonProps?: IconButtonProps;
|
|
25
|
+
childrenContainerClassName?: string;
|
|
26
|
+
inputProps?: InputProps;
|
|
27
|
+
resultClassName?: string;
|
|
28
|
+
resultsClassName?: string;
|
|
29
|
+
resultsContainerClassName?: string;
|
|
30
|
+
withResultsClassName?: string;
|
|
31
|
+
} & SearchBaseProps;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The search logic. To modifiy default classNames look the Search class.
|
|
35
|
+
*/
|
|
36
|
+
function SearchHeadless({ ...props }: SearchProps) {
|
|
37
|
+
const { setLinesIds, setStationId, setTrainId, tenant } = useMapContext();
|
|
38
|
+
const fit = useFit();
|
|
39
|
+
|
|
40
|
+
const onSelectStop = useCallback(
|
|
41
|
+
(stop: StopsFeature) => {
|
|
42
|
+
setStationId(stop.properties.uid);
|
|
43
|
+
// It means that the station wil have informations
|
|
44
|
+
fit.current(stop, !!tenant);
|
|
45
|
+
},
|
|
46
|
+
[fit, setStationId, tenant],
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const onSelectLine = useCallback(
|
|
50
|
+
(line: LnpLineInfo) => {
|
|
51
|
+
setLinesIds([line.external_id]);
|
|
52
|
+
fit.current(line, true);
|
|
53
|
+
},
|
|
54
|
+
[fit, setLinesIds],
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const onSelectTrain = useCallback(
|
|
58
|
+
(match: RealtimeRouteIdentifierMatch) => {
|
|
59
|
+
setTrainId(match.trains[0].train_id);
|
|
60
|
+
fit.current(match, true);
|
|
61
|
+
},
|
|
62
|
+
[fit, setTrainId],
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<SearchBase {...props}>
|
|
67
|
+
<SearchStopsResults>
|
|
68
|
+
<SearchStopsResult onSelectItem={onSelectStop} />
|
|
69
|
+
</SearchStopsResults>
|
|
70
|
+
<SearchLinesResults>
|
|
71
|
+
<SearchLinesResult onSelectItem={onSelectLine} />
|
|
72
|
+
</SearchLinesResults>
|
|
73
|
+
<SearchTrainsResults>
|
|
74
|
+
<SearchTrainsResult onSelectItem={onSelectTrain} />
|
|
75
|
+
</SearchTrainsResults>
|
|
76
|
+
</SearchBase>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
export default memo(SearchHeadless);
|
package/src/Search/index.tsx
CHANGED
|
@@ -0,0 +1,43 @@
|
|
|
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 { ButtonHTMLAttributes, PreactDOMAttributes } from "preact";
|
|
8
|
+
|
|
9
|
+
import type { LnpLineInfo } from "../utils/hooks/useLnp";
|
|
10
|
+
|
|
11
|
+
export type SearchLinesResultProps = {
|
|
12
|
+
className?: string;
|
|
13
|
+
item?: LnpLineInfo;
|
|
14
|
+
onSelectItem?: (line: LnpLineInfo) => void;
|
|
15
|
+
} & ButtonHTMLAttributes<HTMLButtonElement> &
|
|
16
|
+
PreactDOMAttributes;
|
|
17
|
+
|
|
18
|
+
function SearchLinesResult({
|
|
19
|
+
className,
|
|
20
|
+
item,
|
|
21
|
+
onSelectItem,
|
|
22
|
+
...props
|
|
23
|
+
}: SearchLinesResultProps) {
|
|
24
|
+
const { linesNetworkPlanLayer } = useMapContext();
|
|
25
|
+
return (
|
|
26
|
+
<button
|
|
27
|
+
{...props}
|
|
28
|
+
className={twMerge(
|
|
29
|
+
"flex w-full cursor-pointer items-center gap-3 text-left",
|
|
30
|
+
className,
|
|
31
|
+
)}
|
|
32
|
+
onClick={() => {
|
|
33
|
+
linesNetworkPlanLayer?.setVisible(true);
|
|
34
|
+
onSelectItem?.(item);
|
|
35
|
+
}}
|
|
36
|
+
>
|
|
37
|
+
<RouteIcon lineInfo={item}></RouteIcon>
|
|
38
|
+
<div className="grow">{item?.long_name || item?.short_name}</div>
|
|
39
|
+
</button>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export default memo(SearchLinesResult);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./SearchLinesResult";
|