LocationSearch.jsx (3910B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 import React, { useEffect, useRef, useState } from "react"; 6 import { useDispatch, useSelector } from "react-redux"; 7 import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs"; 8 9 function LocationSearch({ outerClassName }) { 10 // should be the location object from suggestedLocations 11 const [selectedLocation, setSelectedLocation] = useState(""); 12 const suggestedLocations = useSelector( 13 state => state.Weather.suggestedLocations 14 ); 15 const locationSearchString = useSelector( 16 state => state.Weather.locationSearchString 17 ); 18 19 const [userInput, setUserInput] = useState(locationSearchString || ""); 20 const inputRef = useRef(null); 21 22 const dispatch = useDispatch(); 23 24 useEffect(() => { 25 if (selectedLocation) { 26 dispatch( 27 ac.AlsoToMain({ 28 type: at.WEATHER_LOCATION_DATA_UPDATE, 29 data: { 30 city: selectedLocation.localized_name, 31 adminName: selectedLocation.administrative_area, 32 country: selectedLocation.country, 33 }, 34 }) 35 ); 36 dispatch(ac.SetPref("weather.query", selectedLocation.key)); 37 dispatch( 38 ac.BroadcastToContent({ 39 type: at.WEATHER_SEARCH_ACTIVE, 40 data: false, 41 }) 42 ); 43 } 44 }, [selectedLocation, dispatch]); 45 46 // when component mounts, set focus to input 47 useEffect(() => { 48 inputRef?.current?.focus(); 49 }, [inputRef]); 50 51 function handleChange(event) { 52 const { value } = event.target; 53 setUserInput(value); 54 55 // if the user input contains less than three characters and suggestedLocations is not an empty array, 56 // reset suggestedLocations to [] so there aren't incorrect items in the datalist 57 if (value.length < 3 && suggestedLocations.length) { 58 dispatch( 59 ac.AlsoToMain({ 60 type: at.WEATHER_LOCATION_SUGGESTIONS_UPDATE, 61 data: [], 62 }) 63 ); 64 } 65 // find match in suggestedLocation array 66 const match = suggestedLocations?.find(({ key }) => key === value); 67 if (match) { 68 setSelectedLocation(match); 69 setUserInput( 70 `${match.localized_name}, ${match.administrative_area.localized_name}` 71 ); 72 } else if (value.length >= 3 && !match) { 73 dispatch( 74 ac.AlsoToMain({ 75 type: at.WEATHER_LOCATION_SEARCH_UPDATE, 76 data: value, 77 }) 78 ); 79 } 80 } 81 82 function handleCloseSearch() { 83 dispatch( 84 ac.BroadcastToContent({ 85 type: at.WEATHER_SEARCH_ACTIVE, 86 data: false, 87 }) 88 ); 89 setUserInput(""); 90 } 91 92 function handleKeyDown(e) { 93 if (e.key === "Escape") { 94 handleCloseSearch(); 95 } 96 } 97 98 return ( 99 <div className={`${outerClassName} location-search`}> 100 <div className="location-input-wrapper"> 101 <div className="search-icon" /> 102 <input 103 ref={inputRef} 104 list="merino-location-list" 105 type="text" 106 data-l10n-id="newtab-weather-change-location-search-input-placeholder" 107 onChange={handleChange} 108 value={userInput} 109 onKeyDown={handleKeyDown} 110 /> 111 <moz-button 112 class="close-icon" 113 type="icon ghost" 114 size="small" 115 iconSrc="chrome://global/skin/icons/close.svg" 116 onClick={handleCloseSearch} 117 /> 118 <datalist id="merino-location-list"> 119 {(suggestedLocations || []).map(merinoLocation => ( 120 <option value={merinoLocation.key} key={merinoLocation.key}> 121 {merinoLocation.localized_name},{" "} 122 {merinoLocation.administrative_area.localized_name} 123 </option> 124 ))} 125 </datalist> 126 </div> 127 </div> 128 ); 129 } 130 131 export { LocationSearch };