Components
Search

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.coordinates for 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 the coordinatePrecision.
  • 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.

OptionDescriptionDefault
resultFormatterType: (result: SearchResult) => string ()Function used to format search results for display.formatResult
onSelectResultType: (result: SearchResult) => void ()Callback fired when a search result is selected.
maxRecentSelectionsType: number ()Maximum number of recent selections to store and expose via recentSelections.3
searchGroupsType: SearchGroupType[] ()Ordered list of result groups to fetch and render.['recent', 'places', 'stations']
coordinatePrecisionType: number ()Decimal precision used when formatting coordinate results.6
queryType: string (optional)Controlled search query value.
onQueryChangeType: (query: string) => void (optional)Callback fired when the search query changes.
recentSelectionsType: SearchResult[] (optional)Controlled list of recent selections.
onRecentSelectionsChangeType: (items: SearchResult[]) => void (optional)Callback fired when the recent selections list changes.
inputRefType: RefObject<HTMLInputElement> (optional)Reference to the search input element. When omitted, an internal ref is used.
onFocusType: () => void (optional)Callback fired when the search input receives focus.
childrenType: ReactNode (required)Search sub-components.

Search.Bar

Container for search input and control elements.

OptionDescriptionDefault
classNameType: string ()Additional CSS classes
childrenType: ReactNode (required)Search bar elements

Search.Input

The search input field.

OptionDescriptionDefault
classNameType: string ()Additional CSS classes
placeholderType: string ()Input placeholder textSearch locations

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.

OptionDescriptionDefault
childrenType: 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.

OptionDescriptionDefault
childrenType: (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.

OptionDescriptionDefault
classNameType: string ()Additional CSS classes.
childrenType: ReactNode (required)Group content, usually including Search.GroupTitle and one or more Search.Item components.

Search.GroupTitle

Simple title component for a Search.Group.

OptionDescriptionDefault
classNameType: string ()Additional CSS classes.
childrenType: 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.

OptionDescriptionDefault
itemType: SearchResult (required)Search result data to render and provide via context.
classNameType: string ()Additional CSS classes applied to the item container.
childrenType: ReactNode (required)Item content, typically including Search.ItemButton and any auxiliary controls.
onClearType: (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.

OptionDescriptionDefault
classNameType: string ()Additional CSS classes applied to the button.
childrenType: ReactNode (optional)Custom button content. When omitted, the label from resultFormatter(item) is used.

Search.Divider

Horizontal line separator between search items.

OptionDescriptionDefault
classNameType: string ()Additional CSS classes

Search.SearchIcon

Icon component specifically designed for the search interface.

OptionDescriptionDefault
colorType: string ()Color of the icon'none'
classNameType: string ()Additional CSS classes

Search.LoadingSpinner

Loading indicator that appears during search operations.

OptionDescriptionDefault
classNameType: string ()Additional CSS classes
sizeType: number ()Size of the spinner in pixels16

Search.Clear

Button to clear the current search input.

OptionDescriptionDefault
classNameType: string ()Additional CSS classes
onClickType: () => 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.

OptionDescriptionDefault
idType: string ()Button ID.'search-geolocate-button'
classNameType: string ()Additional CSS classes.
iconType: IconComponent ()Icon component rendered inside the button.GeoLocateIcon
iconPropsType: IconProps ()Props passed to the icon component.
onClickType: (event: React.MouseEvent<HTMLButtonElement>) => void (optional)Callback fired after a successful geolocation selection or error handling.
onGeoLocationErrorType: (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.

OptionDescriptionDefault
childrenType: ReactNode (required)Scrollable results content.
classNameType: string ()Additional CSS classes applied to the list container.

Search.ScrollableArea

Container component that enables scrolling for search results.

OptionDescriptionDefault
childrenType: ReactNode (required)Scrollable content
classNameType: 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.

OptionDescriptionDefault
childrenType: ReactNode (required)Search bar and results content to be wrapped by the focus boundary.
classNameType: string ()Additional CSS classes applied to the focus area container.