Advanced - Make Your Locator Mobile Responsive | Yext Hitchhikers Platform
What You’ll Learn
In this section, you will:
- Adjust the stying of your StoreLocator component to make it mobile responsive
Overview
Many users are likely going to be using your locator on their mobile devices. Unfortunately, things don’t look too great on smaller screens right now…
In this unit, you’ll make your locator mobile responsive:
Responsive Design with Tailwind
Tailwind Responsive Design makes it possible for you to change how your Locator behaves depending on the size of the user’s viewport. Tailwind responsive utility variants use CSS media queries under the hood to apply different styling based on the size of the viewport.
For example, let’s say that you want the number of columns in a grid container to change based on how wide the screen is; you can achieve that with something like this:
<div className="grid grid-cols-2 md:grid-cols-3 xl:grid-cols-4">
{/* grid elements would go here */}
</div>
Based on the Tailwind classes in this example:
- If the screen is less than 768 pixels wide, the grid will display 2 columns
- If greater than 768 pixels, but fewer than 1280 pixels, the grid will display 3 columns
- If greater than 1280 pixels, the grid will display 4 columns
Install Headless UI
Before you begin, you need to install Headless UI React :
npm i @headlessui/react
Headless UI are series of UI components designed to work with Tailwind. We will be using the
Switch
component
in this module to toggle between the search results and the map on mobile.
1. Make the Map and Search Results Mobile Responsive
In this step, you’re going to change the Tailwind classes in your JSX in order to make the search results and map responsive to changes in the width in the viewport. You’re also going to add the Headless UI Switch
component that will be used to overlay the results on top of the map rather than to the the left when it is toggled.
You’re also going to add the classNames
utility that will allow you to adjust the styling of your components based on changes to variables in your components.
Start by updating your imports to account for the Switch
and classNames
:
// src/components/StoreLocator.tsx
import {
MapboxMap,
FilterSearch,
OnSelectParams,
VerticalResults,
getUserLocation,
OnDragHandler,
} from "@yext/search-ui-react";
import { useEffect, useState } from "react";
import { BiLoaderAlt } from "react-icons/bi";
import {
Matcher,
SelectableStaticFilter,
useSearchActions,
useSearchState,
} from "@yext/search-headless-react";
// Mapbox CSS bundle
import "mapbox-gl/dist/mapbox-gl.css";
import LocationCard from "./LocationCard";
import MapPin from "./MapPin";
import { LngLat, LngLatBounds } from "mapbox-gl";
import { Switch } from "@headlessui/react"; // New
import classNames from "classnames"; // New
// component code...
Then, add the showResults
state variable that will be used in conjunction with the switch to track whether the results or map should be visible on mobile:
// src/components/StoreLocator.tsx
// imports...
const StoreLocator = (): JSX.Element => {
const [showResults, setShowResults] = useState(true);
// other code...
}
Finally, update your JSX with new Tailwind utility classes:
// src/components/StoreLocator.tsx
// imports...
const StoreLocator = (): JSX.Element => {
// other code...
return (
<>
<div className="relative flex h-[calc(100vh-210px)] flex-col border md:flex-row ">
{initialSearchState !== "complete" && (
<div className="absolute z-20 flex h-full w-full items-center justify-center bg-white opacity-70">
<BiLoaderAlt className="animate-spin " size={64} />
</div>
)}
<div className="flex w-full flex-col overflow-y-auto md:w-1/3">
<FilterSearch
onSelect={handleFilterSelect}
placeholder="Find Locations Near You"
searchFields={[
{
entityType: "location",
fieldApiName: "builtin.location",
},
]}
/>
<div className="z-[5] flex w-full justify-center space-x-2 bg-zinc-100 py-4 shadow-lg md:hidden">
<p>Map</p>
<Switch
checked={showResults}
onChange={setShowResults}
className={`${
showResults ? "bg-orange" : "bg-zinc-300"
} relative inline-flex h-6 w-11 items-center rounded-full`}
>
<span
aria-hidden="true"
className={`${
showResults ? "translate-x-6 " : "translate-x-1 bg-orange"
} inline-block h-4 w-4 transform rounded-full bg-white transition`}
/>
</Switch>
<p>Results</p>
</div>
<div
className={classNames(
"absolute top-[100px] bg-white left-0 right-0 bottom-0 overflow-hidden overflow-y-auto md:static",
{ "z-[5]": showResults }
)}
>
{resultCount > 0 && (
<VerticalResults CardComponent={LocationCard} />
)}
{resultCount === 0 && initialSearchState === "complete" && (
<div className="flex items-center justify-center">
<p className="pt-4 text-2xl">No results found for this area</p>
</div>
)}
</div>
</div>
<div className="relative h-[calc(100vh-310px)] w-full md:h-full md:w-2/3">
<MapboxMap
mapboxAccessToken={YEXT_PUBLIC_MAPBOX_API_KEY || ""}
PinComponent={MapPin}
onDrag={handleDrag}
/>
{showSearchAreaButton && (
<div className="absolute top-10 left-0 right-0 flex justify-center">
<button
onClick={handleSearchAreaClick}
className="rounded-2xl border bg-white py-2 px-4 shadow-xl"
>
<p>Search This Area</p>
</button>
</div>
)}
</div>
</div>
</>
);
}
Let’s review what you just added:
flex-col
andmd:flex-row
were added to the outer mostdiv
. This means that the flex box will be laid out as a column on smaller screens and a column on larger ones.w-1/3
was replaced withmd:w-1/3
andw-full
was added to thediv
that containsFilterSearch
,VerticalResults
, and theSwitch
. The container will take up the entire width of its parent on mobile but only 1/3rd of its parent (like before) on larger screens.- The
Switch
was added to toggle between the results and map on mobile sized screens. VerticalResults
has been wrapped in adiv
that positions the search results right below theFilterSearch
bar andSwitch
on mobile by using absolute positioning. It will remain statically positioned on larger screens. Results visible whenshowResults
equals true as this will toggle the z-index of the container.- The
div
that contains theMapboxMap
replacedw-2/3
withmd:w-2/3
and addedh-[calc(100vh-310px)]
,w-full
, andmd:h-full
. The container will take up the entire width of its parent on mobile but only 2/3rds of its parent container (like before) on larger screens. Since you need to reduce the height of the map to account for the filter search and switch containers on mobile, the map container needs to be set accordingly on mobile and take up the full height of its container on larger screens.
If your site isn’t already running, start it back up with npm run dev
and go to the locator page. After the map loads for the first time, shrink your viewport and check out how things look on a mobile sized screen.
Try toggling between Map and Results while testing out some of the features you added in previous units.
Finally, expand the viewport back to normal size and see how the locator adjusts accordingly.