The Menu component displays a searchable list of countries organized by region. It includes a search bar, loading states, and a fullscreen toggle for mobile devices.
Overview
The Menu component:
- Organizes countries by geographic region
- Provides search functionality to filter countries
- Displays progress indicators for each region
- Supports fullscreen mode on mobile devices
- Shows loading and empty states
Props
interface Props {
loading?: boolean | undefined;
fullscreen: boolean;
toggleFullscreen: () => void;
}
Controls whether the loading spinner is displayed
Current fullscreen state of the menu on mobile devices
Callback function to toggle fullscreen mode on mobile
Component Signature
export const Menu: FC<Props> = memo(
({ loading = false, fullscreen, toggleFullscreen }) => {
// Component implementation
}
);
Features
Search Functionality
The menu includes a search input that filters countries by name:
const [search, setSearch] = useState('');
const filteredRegions = useMemo(() => {
const s = search.trim().toLowerCase();
if (!s) {
return regions;
}
return regions
.map((region) => ({
...region,
values: region.values.filter(({ name }) =>
name.toLowerCase().includes(s),
),
}))
.filter((region) => region.values.length > 0);
}, [search, regions]);
Regional Organization
Countries are organized by region with sticky headers:
{filteredRegions.map((region) => (
<li key={region.name}>
<div className="sticky top-0 bg-zinc-200 dark:bg-zinc-800">
<h2>{region.name || 'Other'}</h2>
<Progress complete={region.complete ?? 0} />
</div>
<ul>
{region.values.map((country) => (
<MenuItem key={country.iso3166} country={country} />
))}
</ul>
</li>
))}
Mobile Fullscreen Toggle
On mobile devices, a button allows expanding the menu to fullscreen:
<button
type="button"
className="visible p-2 md:hidden"
onClick={toggleFullscreen}
>
{fullscreen ? <ContractIcon /> : <ExpandIcon />}
</button>
States
Loading State
Displays an animated spinner while data is loading:
{loading ? (
<div className="flex size-full items-center justify-center">
<svg className="size-5 animate-spin">
{/* Spinner SVG */}
</svg>
</div>
) : (
// Menu content
)}
Empty State
Shows a message when search returns no results:
{filteredRegions.length === 0 ? (
<div className="m-4 h-full text-center font-medium">
No results!
</div>
) : (
// Country list
)}
Usage Example
import { Menu } from './components/menu';
import { useState } from 'react';
function App() {
const [loading, setLoading] = useState(false);
const [fullscreen, setFullscreen] = useState(false);
return (
<Menu
loading={loading}
fullscreen={fullscreen}
toggleFullscreen={() => setFullscreen(!fullscreen)}
/>
);
}
Layout Structure
The menu consists of three main sections:
- Search Bar - Text input with optional fullscreen toggle
- Loading/Empty State - Conditional rendering based on state
- Country List - Scrollable list organized by region
<>
<div className="flex border-t-2">
<form>Search input</form>
<button>Fullscreen toggle</button>
</div>
{loading ? (
<LoadingSpinner />
) : filteredRegions.length === 0 ? (
<EmptyState />
) : (
<CountryList />
)}
</>
Accessibility
- Search input has a proper
<label> with sr-only class for screen readers
- SVG icons include
<title> elements
- Semantic HTML structure with proper heading hierarchy
<label htmlFor={searchId} className="sr-only">
Search
</label>
<input id={searchId} type="text" placeholder="Search..." />
Styling
The menu uses Tailwind CSS with responsive design:
- Mobile-first approach with
md: breakpoint modifiers
- Dark mode support with
dark: variant
- Sticky region headers that remain visible during scroll
- Smooth hover effects on country items
State Integration
The Menu component uses the regionsAtom from Jotai to access the organized country data:
const regions = useAtomValue(regionsAtom);
Dependencies
jotai - State management
react - Core React functionality
MenuItem - Child component for individual countries
Progress - Progress indicator component
Source Code
Location: src/components/menu.tsx