-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathBottomSheet.jsx
More file actions
253 lines (230 loc) · 10.9 KB
/
BottomSheet.jsx
File metadata and controls
253 lines (230 loc) · 10.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
import { useEffect, useRef } from 'react';
import { useState } from 'react';
import { ContainerContext } from './ContainerContext';
import { useRecoilState, useSetRecoilState } from 'recoil';
import currentLocationState from '../../atoms/currentLocationState';
import filteredLocationsByExternalIDState from '../../atoms/filteredLocationsByExternalIDState';
import Sheet from './Sheet/Sheet';
import './BottomSheet.scss';
import LocationDetails from '../LocationDetails/LocationDetails';
import Wayfinding from '../Wayfinding/Wayfinding';
import Directions from '../Directions/Directions';
import Search from '../Search/Search';
import LocationsList from '../LocationsList/LocationsList';
import ChatWindow from '../ChatWindow/ChatWindow';
import locationIdState from '../../atoms/locationIdState';
import { useChatLocations, useChatDirections } from '../../hooks/useChat';
import PropTypes from 'prop-types';
import { snapPoints } from '../../constants/snapPoints';
BottomSheet.propTypes = {
directionsFromLocation: PropTypes.string,
directionsToLocation: PropTypes.string,
pushAppView: PropTypes.func.isRequired,
currentAppView: PropTypes.string,
appViews: PropTypes.object,
onRouteFinished: PropTypes.func.isRequired
};
/**
* The BottomSheet component is responsible for rendering other components (Search, Wayfinding etc.) in a bottom sheet.
* It is used on smaller screens. On larger screens, the Sidebar component is used.
*
* All components are wrapped in a Sheet component, which handles user interactions (swiping to control the sheet size etc.).
*
* @param {Object} props
* @param {string} props.directionsFromLocation - Origin Location to be used to instantly show directions.
* @param {string} props.directionsToLocation - Destination Location to be used to instantly show directions.
* @param {function} props.pushAppView - Function to push to app view to browser history.
* @param {string} props.currentAppView - Holds the current view/state of the Map Template.
* @param {array} props.appViews - Array of all possible views.
* @param {function} props.onRouteFinished - Callback that fires when the route has finished.
*
*/
function BottomSheet({ directionsFromLocation, directionsToLocation, pushAppView, currentAppView, appViews, onRouteFinished }) {
const bottomSheetRef = useRef();
// References to the Sheet components for each individual component.
const searchSheetRef = useRef();
const locationsListSheetRef = useRef();
const locationDetailsSheetRef = useRef();
const wayfindingSheetRef = useRef();
const directionsSheetRef = useRef();
const [currentLocation, setCurrentLocation] = useRecoilState(currentLocationState);
// Use chat hooks for handling chat interactions
const handleChatSearchResults = useChatLocations();
const handleChatShowRoute = useChatDirections(pushAppView, appViews);
// Holds boolean depicting if the current Location contains more information than just the basic info that is shown in minimal height bottom sheet.
const [currentLocationIsDetailed, setCurrentLocationIsDetailed] = useState(false);
const [filteredLocationsByExternalIDs, setFilteredLocationsByExternalID] = useRecoilState(filteredLocationsByExternalIDState);
const setLocationId = useSetRecoilState(locationIdState);
/*
* React on changes on the current location and directions locations and set relevant bottom sheet.
*/
useEffect(() => {
if (directionsFromLocation && directionsToLocation && currentAppView === appViews.DIRECTIONS) return; // Never change sheet when dependencies change within Directions.
if (directionsFromLocation && directionsToLocation) {
pushAppView(appViews.WAYFINDING);
} else if (directionsFromLocation) {
pushAppView(appViews.WAYFINDING);
} else if (currentLocation) {
pushAppView(appViews.LOCATION_DETAILS, currentLocation);
} else if (filteredLocationsByExternalIDs?.length > 1) {
pushAppView(appViews.EXTERNALIDS);
// If there is only one external ID, behave the same as having the location ID prop.
} else if (filteredLocationsByExternalIDs?.length === 1) {
setCurrentLocation(filteredLocationsByExternalIDs[0])
setLocationId(filteredLocationsByExternalIDs[0].id)
} else {
pushAppView(appViews.SEARCH);
}
}, [currentLocation, directionsFromLocation, directionsToLocation, filteredLocationsByExternalIDs]);
/*
* React on changes on the current location and check if it contains more information than just the basic info that is shown in minimal height bottom sheet.
* If that is the case, we show a little more to indicate to the user that there is more information to be seen.
*/
useEffect(() => {
if (currentLocation) {
const isDetailed = currentLocation.properties.imageURL
|| currentLocation.properties.description
|| currentLocation.properties.additionalDetails
|| Object.keys(currentLocation.properties.categories).length > 0;
setCurrentLocationIsDetailed(isDetailed);
}
}, [currentLocation]);
/**
* Close the location details page and navigate to either the Locations list page or the Search page.
*/
function closeLocationDetails() {
if (filteredLocationsByExternalIDs?.length > 1) {
pushAppView(appViews.EXTERNALIDS);
setCurrentLocation();
} else if (filteredLocationsByExternalIDs?.length === 1) {
pushAppView(appViews.SEARCH);
setCurrentLocation();
setFilteredLocationsByExternalID([]);
// Reset the search sheet height to its minimum size when closing location details
searchSheetRef.current?.setSnapPoint(snapPoints.MIN);
} else {
pushAppView(appViews.SEARCH);
setCurrentLocation();
searchSheetRef.current?.setSnapPoint(snapPoints.MIN);
}
}
/**
* Close the Locations list page and navigate to the Search page, resetting the filtered locations.
*/
function closeLocationsList() {
pushAppView(appViews.SEARCH);
setCurrentLocation();
setFilteredLocationsByExternalID([]);
}
/**
* Render the appropriate sheet based on the current app view.
*/
function renderCurrentSheet() {
switch (currentAppView) {
case appViews.SEARCH:
return (
<Sheet
minimizedHeight={80}
initialSnapPoint={snapPoints.MIN}
key="SEARCH"
isOpen={true}
ref={searchSheetRef}
>
<Search
isOpen={true}
onSetSize={size => searchSheetRef.current?.setSnapPoint(size)}
onOpenChat={() => pushAppView(appViews.CHAT)}
/>
</Sheet>
);
case appViews.EXTERNALIDS:
return (
<Sheet
minimizedHeight={200}
initialSnapPoint={snapPoints.MIN}
key="EXTERNALIDS"
isOpen={true}
ref={locationsListSheetRef}
>
<LocationsList
onSetSize={size => locationsListSheetRef.current?.setSnapPoint(size)}
onBack={() => closeLocationsList()}
locations={filteredLocationsByExternalIDs}
onLocationClick={location => setCurrentLocation(location)}
/>
</Sheet>
);
case appViews.LOCATION_DETAILS:
return (
<Sheet
minimizedHeight={currentLocationIsDetailed ? 180 : 136}
key="LOCATION_DETAILS"
initialSnapPoint={snapPoints.MIN}
isOpen={true}
ref={locationDetailsSheetRef}
>
<LocationDetails
onSetSize={size => locationDetailsSheetRef.current?.setSnapPoint(size)}
onStartWayfinding={() => pushAppView(appViews.WAYFINDING)}
onBack={() => closeLocationDetails()}
onStartDirections={() => pushAppView(appViews.DIRECTIONS)}
isOpen={true}
/>
</Sheet>
);
case appViews.WAYFINDING:
return (
<Sheet
minimizedHeight={190}
key="WAYFINDING"
initialSnapPoint={snapPoints.FIT}
isOpen={true}
ref={wayfindingSheetRef}
>
<Wayfinding
onSetSize={size => wayfindingSheetRef.current?.setSnapPoint(size)}
onStartDirections={() => pushAppView(appViews.DIRECTIONS)}
directionsToLocation={directionsToLocation}
directionsFromLocation={directionsFromLocation}
onBack={() => pushAppView(currentLocation ? appViews.LOCATION_DETAILS : appViews.SEARCH)}
isActive={true}
/>
</Sheet>
);
case appViews.DIRECTIONS:
return (
<Sheet
minimizedHeight={273}
initialSnapPoint={snapPoints.FIT}
ref={directionsSheetRef}
key="DIRECTIONS"
isOpen={true}
>
<Directions
onSetSize={size => directionsSheetRef.current?.setSnapPoint(size)}
isOpen={true}
onBack={() => pushAppView(appViews.WAYFINDING)}
onRouteFinished={() => onRouteFinished()}
/>
</Sheet>
);
case appViews.CHAT:
return (
<ChatWindow
isVisible
onClose={() => pushAppView(appViews.SEARCH)}
onSearchResults={handleChatSearchResults}
onShowRoute={handleChatShowRoute}
/>
);
default:
return null;
}
}
return <div ref={bottomSheetRef} className="bottom-sheets">
<ContainerContext.Provider value={bottomSheetRef}>
{renderCurrentSheet()}
</ContainerContext.Provider>
</div>
}
export default BottomSheet;