Search
The Search component provides a flexible, composable interface for location-based searching, powered by the Xweather API places (opens in a new tab) and observations (opens in a new tab) endpoints when used with Search.ResultsFetcher.
Features
- Text-based location search with autocomplete
- Support for coordinates (Decimal, DMS), ZIP codes, and airport codes
- Recent selection history
- Grouped search results (Places, Stations)
- Loading indicators during API requests
- Custom result formatting via
resultFormatter
Usage Examples
Basic Search Implementation
import { Search } from '@xweather/maps-ui-sdk';
const SearchExample = () => {
const handleSelectResult = (result: SearchResult) => {
const { lat, lon } = result.coordinates;
// Handle the selected location
};
return (
<Search onSelectResult={handleSelectResult}>
<Search.Bar>
<Search.SearchIcon />
<Search.Input placeholder="Search for a location" />
<Search.LoadingSpinner />
<Search.Clear />
<Search.GeolocateButton />
</Search.Bar>
<Search.ResultsFetcher>
<Search.ResultsData>
{({ places, stations, recentSelections, hasResults }) => (
<Search.ScrollableArea>
{recentSelections.length > 0 && (
<Search.Group>
<Search.GroupTitle>Recent</SearchGroupTitle>
{recentSelections.map((item) => (
<Search.Item key={item.trackingId} item={item}>
<Search.ItemButton />
</Search.Item>
))}
</Search.Group>
)}
{hasResults && (
<>
<Search.Group>
<Search.GroupTitle>Places</Search.GroupTitle>
{places.map((item) => (
<Search.Item key={item.trackingId} item={item}>
<Search.ItemButton />
</Search.Item>
))}
</Search.Group>
<Search.Group>
<Search.GroupTitle>Stations</Search.GroupTitle>
{stations.map((item) => (
<Search.Item key={item.trackingId} item={item}>
<Search.ItemButton />
</Search.Item>
))}
</Search.Group>
</>
)}
</Search.ScrollableArea>
)}
</Search.ResultsData>
</Search.ResultsFetcher>
</Search>
);
};Interactive Search with Tabs
Here's an example of integrating the Search component within a tabbed interface:
import { useEffect, useState, useRef } from 'react';
import { Search, Tabs } from '@xweather/maps-ui-sdk';
import { type Coordinates } from '@xweather/maps-ui-sdk/dist/types/location';
const TabbedSearch = () => {
const [currentTab, setCurrentTab] = useState<string | null>(null);
const searchInputRef = useRef<HTMLInputElement>(null);
const [coordinates, setCoordinates] = useState<Coordinates | null>(null);
// Auto-focus search input when tab is selected
useEffect(() => {
if (currentTab === 'search') {
searchInputRef.current?.focus();
}
}, [currentTab]);
const handleSelectResult = (result) => setCoordinates(result.coordinates);
return (
<Tabs.Provider value={currentTab} onChange={setCurrentTab}>
<Tabs.List>
<Tabs.Button value="search">Search Tab</Tabs.Button>
<Tabs.Button value="settings">Settings Tab</Tabs.Button>
</Tabs.List>
<Tabs.Content value="search">
<Search
className="sm:w-90"
inputRef={searchInputRef}
onSelectResult={handleSelectResult}
>
<Search.Bar>
<Search.SearchIcon />
<Search.Input />
<Search.LoadingSpinner />
<Search.Clear />
<Search.GeolocateButton />
</Search.Bar>
<Search.ResultsFetcher>
<Search.ResultsData>
{/* Results rendering */}
</Search.ResultsData>
</Search.ResultsFetcher>
</Search>
</Tabs.Content>
</Tabs.Provider>
);
};Tip: Use
result.coordinatesfor panning/zooming the map. It is populated for both API-backed results and pure coordinate searches, even when no place metadata is available.
Custom Result Formatting
By default, the Search component generates labels based on the result type:
- Coordinate queries render normalized coordinates (e.g.
40.446195, -79.948862) with 6 decimal places. This can be customized with thecoordinatePrecision. - Place results render
"Name, State, Country", with:- City name capitalized.
- US state codes uppercased.
- Country omitted for US results.
- Station-like results fall back to a station ID.
You can override this behavior with the resultFormatter prop:
const resultFormatter = (result: SearchResult) => {
// Coordinate queries: "Lat: 40.442, Lon: -79.954"
if (result.queryMeta?.type === 'coordinate') {
const { lat, lon } = result.coordinates;
return `Lat: ${lat.toFixed(3)}, Lon: ${lon.toFixed(3)}`;
}
// Place results: "Minneapolis - MN"
if (result.place && 'place' in result) {
const { place } = result;
const parts = [
place.name?.trim(),
place.state?.toUpperCase(),
place.country === 'US' ? null : place.countryFull,
].filter(Boolean);
return parts.join(' - ');
}
// Station results: "Station: KMSP"
if (result.id && 'id' in result) {
const stationId = result.id.replace(/^.*?_/, '');
return `Station: ${stationId}`;
}
return '';
};
const SearchWithCustomFormat = () => (
<Search resultFormatter={resultFormatter}>
{/* Search components */}
</Search>
);API Reference
Search (Root)
The main wrapper component that provides search context to all child components.
| Option | Description | Default |
|---|---|---|
resultFormatter | Type: (result: SearchResult) => string ()Function used to format search results for display. | |
onSelectResult | Type: (result: SearchResult) => void ()Callback fired when a search result is selected. | |
maxRecentSelections | Type: number ()Maximum number of recent selections to store and expose via recentSelections. | |
searchGroups | Type: SearchGroupType[] ()Ordered list of result groups to fetch and render. | |
coordinatePrecision | Type: number ()Decimal precision used when formatting coordinate results. | |
query | Type: string (optional)Controlled search query value. | |
onQueryChange | Type: (query: string) => void (optional)Callback fired when the search query changes. | |
recentSelections | Type: SearchResult[] (optional)Controlled list of recent selections. | |
onRecentSelectionsChange | Type: (items: SearchResult[]) => void (optional)Callback fired when the recent selections list changes. | |
inputRef | Type: RefObject<HTMLInputElement> (optional)Reference to the search input element. When omitted, an internal ref is used. | |
onFocus | Type: () => void (optional)Callback fired when the search input receives focus. | |
children | Type: ReactNode (required)Search sub-components. | |
Search.Bar
Container for search input and control elements.
| Option | Description | Default |
|---|---|---|
className | Type: string ()Additional CSS classes | |
children | Type: ReactNode (required)Search bar elements | |
Search.Input
The search input field.
| Option | Description | Default |
|---|---|---|
className | Type: string ()Additional CSS classes | |
placeholder | Type: string ()Input placeholder text | |
Search.ResultsFetcher
A wrapper that builds and executes Xweather API search requests (places and, optionally, observations) based on the current query and searchGroups. It wires results into Search.ResultsData and integrates with the loading state.
| Option | Description | Default |
|---|---|---|
children | Type: ReactNode (required)Components that need access to the fetched data | |
Search.ResultsData
A component that processes and provides search results data to its children through a render prop pattern. It manages both API search results and recently selected locations, transforming raw API responses into a consumable format.
| Option | Description | Default |
|---|---|---|
children | Type: (props: { results: SearchResult[], recentSelections: SearchResult[], hasResults: boolean }) => ReactNode (required)Render prop function that receives the processed search results, recently selected locations, and a boolean indicating if there are any results to display | |
Search.Group
Groups related search results. Typically used together with Search.GroupTitle to label a section of results.
| Option | Description | Default |
|---|---|---|
className | Type: string ()Additional CSS classes. | |
children | Type: ReactNode (required)Group content, usually including Search.GroupTitle and one or more Search.Item components. | |
Search.GroupTitle
Simple title component for a Search.Group.
| Option | Description | Default |
|---|---|---|
className | Type: string ()Additional CSS classes. | |
children | Type: ReactNode (required)Text or elements to render as the group title. | |
Search.Item
Container for a single search result. Provides the result data via context, manages active/hover state, and optionally wires clear behavior for recent selections.
| Option | Description | Default |
|---|---|---|
item | Type: SearchResult (required)Search result data to render and provide via context. | |
className | Type: string ()Additional CSS classes applied to the item container. | |
children | Type: ReactNode (required)Item content, typically including Search.ItemButton and any auxiliary controls. | |
onClear | Type: (item: SearchResult) => void (optional)Callback fired when the item is cleared. When provided, the item exposes clear behavior through SearchClearContext, allowing Search.Clear to remove this item. | |
Search.ItemButton
Button used inside Search.Item to handle selection. It triggers the root onSelectResult callback and can be customized with children. If you override the content, ensure the visible text still matches resultFormatter(item) for accessible combobox behavior.
| Option | Description | Default |
|---|---|---|
className | Type: string ()Additional CSS classes applied to the button. | |
children | Type: ReactNode (optional)Custom button content. When omitted, the label from resultFormatter(item) is used. | |
Search.Divider
Horizontal line separator between search items.
| Option | Description | Default |
|---|---|---|
className | Type: string ()Additional CSS classes | |
Search.SearchIcon
Icon component specifically designed for the search interface.
| Option | Description | Default |
|---|---|---|
color | Type: string ()Color of the icon | |
className | Type: string ()Additional CSS classes | |
Search.LoadingSpinner
Loading indicator that appears during search operations.
| Option | Description | Default |
|---|---|---|
className | Type: string ()Additional CSS classes | |
size | Type: number ()Size of the spinner in pixels | |
Search.Clear
Button to clear the current search input.
| Option | Description | Default |
|---|---|---|
className | Type: string ()Additional CSS classes | |
onClick | Type: () => void ()Custom click handler | |
Search.GeolocateButton
Button that triggers a geolocation-based search using the browser’s Geolocation API and the Xweather Weather API places endpoint. Intended for use within a MapsGL context.
| Option | Description | Default |
|---|---|---|
id | Type: string ()Button ID. | |
className | Type: string ()Additional CSS classes. | |
icon | Type: IconComponent ()Icon component rendered inside the button. | |
iconProps | Type: IconProps ()Props passed to the icon component. | |
onClick | Type: (event: React.MouseEvent<HTMLButtonElement>) => void (optional)Callback fired after a successful geolocation selection or error handling. | |
onGeoLocationError | Type: (error: GeolocationPositionError) => void (optional)Callback fired when a geolocation request fails (for example, when the user denies permission or an error occurs). | |
Search.List
Accessible container for the visible recent/results list. Handles listbox roles and toggles the expanded state used by Search.Input.
| Option | Description | Default |
|---|---|---|
children | Type: ReactNode (required)Scrollable results content. | |
className | Type: string ()Additional CSS classes applied to the list container. | |
Search.ScrollableArea
Container component that enables scrolling for search results.
| Option | Description | Default |
|---|---|---|
children | Type: ReactNode (required)Scrollable content | |
className | Type: string ()Additional CSS classes | |
Search.FocusArea
Manages focus and expanded state for the search UI. When focus enters this area, results are shown (when available); when focus leaves, results are hidden and the active descendant is cleared.
| Option | Description | Default |
|---|---|---|
children | Type: ReactNode (required)Search bar and results content to be wrapped by the focus boundary. | |
className | Type: string ()Additional CSS classes applied to the focus area container. | |