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
JavaScript
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