UNPKG

tiny-location-input

Version:

Ultra-lightweight React location autocomplete input with inline styling, geonames integration, and full TypeScript support — perfect for modern UIs.

130 lines (123 loc) 3.79 kB
import React, { useState, useRef, useEffect } from 'react'; // Default inline styles for the component const defaultStyles = { container: { position: 'relative', width: '600px', borderRadius: '4px' }, input: { width: '100%', padding: '8px', fontSize: '16px', boxSizing: 'border-box', borderRadius: '4px' }, suggestions: { position: 'absolute', width: '100%', maxHeight: '300px', overflowY: 'auto', border: '1px solid #ccc', backgroundColor: 'white', zIndex: 1000, marginTop: '4px' }, suggestionItem: { padding: '8px', cursor: 'pointer', ':hover': { backgroundColor: '#f0f0f0' } } }; function LocationInput({ onSelect, placeholder = "Start typing a city name...", classNames = {}, styles = {} }) { const [query, setQuery] = useState(''); const [suggestions, setSuggestions] = useState([]); const [showSuggestions, setShowSuggestions] = useState(false); const containerRef = useRef(null); const debounceTimer = useRef(null); // Fetch location data from API const fetchLocations = async q => { try { // Note: using %22 to encode the quotes const url = `https://public.opendatasoft.com/api/explore/v2.1/catalog/datasets/geonames-all-cities-with-a-population-1000/records?select=name,cou_name_en,coordinates&where=search(name,"${q}")&limit=10`; const response = await fetch(url); const data = await response.json(); setSuggestions(data.results || []); setShowSuggestions(true); } catch (error) { console.error('Error fetching locations:', error); setShowSuggestions(false); } }; // Handle input change with debounce const handleInputChange = e => { const value = e.target.value; setQuery(value); if (debounceTimer.current) clearTimeout(debounceTimer.current); if (value.trim().length < 2) { setShowSuggestions(false); return; } debounceTimer.current = setTimeout(() => { fetchLocations(value.trim()); }, 300); }; // When a suggestion is clicked, update the input and call onSelect const handleSelectItem = location => { const label = `${location.name}, ${location.cou_name_en}`; setQuery(label); setShowSuggestions(false); onSelect && onSelect(location); }; // Close suggestions if clicked outside useEffect(() => { const handleClickOutside = e => { if (containerRef.current && !containerRef.current.contains(e.target)) { setShowSuggestions(false); } }; document.addEventListener('click', handleClickOutside); return () => document.removeEventListener('click', handleClickOutside); }, []); return /*#__PURE__*/React.createElement("div", { ref: containerRef, className: classNames.container, style: { ...defaultStyles.container, ...(styles.container || {}) } }, /*#__PURE__*/React.createElement("input", { type: "text", value: query, onChange: handleInputChange, placeholder: placeholder, className: classNames.input, style: { ...defaultStyles.input, ...(styles.input || {}) } }), showSuggestions && suggestions.length > 0 && /*#__PURE__*/React.createElement("div", { className: classNames.suggestions, style: { ...defaultStyles.suggestions, ...(styles.suggestions || {}) } }, suggestions.map((location, index) => /*#__PURE__*/React.createElement("div", { key: index, className: classNames.suggestionItem, style: { ...defaultStyles.suggestionItem, ...(styles.suggestionItem || {}) }, onClick: () => handleSelectItem(location) }, location.name, ", ", location.cou_name_en)))); } export { LocationInput as default }; //# sourceMappingURL=index.js.map