From 480d1dcaac35cdc40b98e32a0010a22dd038f674 Mon Sep 17 00:00:00 2001 From: Edvin Gunic Date: Mon, 14 Sep 2026 04:10:04 +0200 Subject: [PATCH 1/2] feat: add accessible country selector Extract a reusable country selector with props, an associated visible label and keyboard focus styling. Keep selection synchronized with the active country. Closes #16 --- country-explorer/src/components/App.css | 20 +------- country-explorer/src/components/App.tsx | 23 +++------- .../src/components/CountrySelector.css | 24 ++++++++++ .../src/components/CountrySelector.tsx | 46 +++++++++++++++++++ 4 files changed, 78 insertions(+), 35 deletions(-) create mode 100644 country-explorer/src/components/CountrySelector.css create mode 100644 country-explorer/src/components/CountrySelector.tsx diff --git a/country-explorer/src/components/App.css b/country-explorer/src/components/App.css index 8f26ef0..c1321cc 100644 --- a/country-explorer/src/components/App.css +++ b/country-explorer/src/components/App.css @@ -1,19 +1 @@ -.country-selection { - display: flex; - flex-wrap: wrap; - align-items: center; - justify-content: center; - gap: 0.5rem; - margin: 1rem; -} - -.country-selection select { - min-width: 0; - max-width: 100%; - padding: 0.5rem; - font: inherit; - color: var(--text-h); - background: var(--bg); - border: 1px solid var(--border); - border-radius: 4px; -} +/* Main layout styles go here. */ diff --git a/country-explorer/src/components/App.tsx b/country-explorer/src/components/App.tsx index a0b019f..8521f7e 100644 --- a/country-explorer/src/components/App.tsx +++ b/country-explorer/src/components/App.tsx @@ -2,6 +2,7 @@ import './App.css'; import { useState } from 'react'; import { useCountries } from '../hooks/useCountries'; import CountryCard from './CountryCard'; +import CountrySelector from './CountrySelector'; import Loading from './Loading'; import ErrorMessage from './ErrorMessage'; import NavigationControls from './NavigationControls'; @@ -40,22 +41,12 @@ function App() {

Filters and selection

{/* RegionFilter and SortSelector go here */} -
- - -
+
diff --git a/country-explorer/src/components/CountrySelector.css b/country-explorer/src/components/CountrySelector.css new file mode 100644 index 0000000..5bd5534 --- /dev/null +++ b/country-explorer/src/components/CountrySelector.css @@ -0,0 +1,24 @@ +.country-selection { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: center; + gap: 0.5rem; + margin: 1rem; +} + +.country-selection select { + min-width: 0; + max-width: 100%; + padding: 0.5rem; + font: inherit; + color: var(--text-h); + background: var(--bg); + border: 1px solid var(--border); + border-radius: 4px; +} + +.country-selection select:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 3px; +} diff --git a/country-explorer/src/components/CountrySelector.tsx b/country-explorer/src/components/CountrySelector.tsx new file mode 100644 index 0000000..3b24caf --- /dev/null +++ b/country-explorer/src/components/CountrySelector.tsx @@ -0,0 +1,46 @@ +import { useId } from 'react'; +import type { Country } from '../types/country'; +import './CountrySelector.css'; + +interface CountrySelectorProps { + countries: Country[]; + selectedCountryCode: string | null; + onSelectCountry: (code: string) => void; + disabled?: boolean; +} + +function CountrySelector({ + countries, + selectedCountryCode, + onSelectCountry, + disabled = false, +}: CountrySelectorProps) { + const selectId = useId(); + + return ( +
+ + +
+ ); +} + +export default CountrySelector; From 7d467f9e7f8c32eee73b5062dc37b4f911e9687a Mon Sep 17 00:00:00 2001 From: Edvin Gunic Date: Mon, 14 Sep 2026 05:19:15 +0200 Subject: [PATCH 2/2] feat: add region filtering Filter European countries by subregion without mutating API data. Synchronize country selection and navigation, provide an all-regions option, and handle empty results. Closes #28 --- country-explorer/src/components/App.tsx | 44 +++++++++++++++---- .../src/components/RegionFilter.css | 24 ++++++++++ .../src/components/RegionFilter.tsx | 39 ++++++++++++++++ 3 files changed, 98 insertions(+), 9 deletions(-) create mode 100644 country-explorer/src/components/RegionFilter.css create mode 100644 country-explorer/src/components/RegionFilter.tsx diff --git a/country-explorer/src/components/App.tsx b/country-explorer/src/components/App.tsx index 8521f7e..0415156 100644 --- a/country-explorer/src/components/App.tsx +++ b/country-explorer/src/components/App.tsx @@ -3,24 +3,40 @@ import { useState } from 'react'; import { useCountries } from '../hooks/useCountries'; import CountryCard from './CountryCard'; import CountrySelector from './CountrySelector'; +import RegionFilter from './RegionFilter'; import Loading from './Loading'; import ErrorMessage from './ErrorMessage'; import NavigationControls from './NavigationControls'; function App() { const { countries, isLoading, isError, error, refetch } = useCountries(); + const [selectedRegion, setSelectedRegion] = useState(''); + // The fetched countries are European, so filter by their geographic subregion. + const getRegion = (country: (typeof countries)[number]) => + country.subregion?.trim() || country.region?.trim() || 'Unknown region'; + const regions = [...new Set(countries.map(getRegion))].sort(); + const availableRegions = + regions.includes(selectedRegion) || !selectedRegion + ? regions + : [...regions, selectedRegion].sort(); + const filteredCountries = selectedRegion + ? countries.filter((country) => getRegion(country) === selectedRegion) + : countries; const [selectedCountryCode, setSelectedCountryCode] = useState(null); const selectedCountry = - countries.find((country) => country.code === selectedCountryCode) ?? countries[0]; + filteredCountries.find((country) => country.code === selectedCountryCode) ?? + filteredCountries[0]; const activeCountryCode = selectedCountry?.code ?? null; - const activeCountryIndex = countries.findIndex((country) => country.code === activeCountryCode); + const activeCountryIndex = filteredCountries.findIndex( + (country) => country.code === activeCountryCode, + ); function navigateCountry(direction: -1 | 1) { setSelectedCountryCode((currentCode) => { - const currentIndex = countries.findIndex((country) => country.code === currentCode); - if (currentIndex === -1) return countries[0]?.code ?? null; + const currentIndex = filteredCountries.findIndex((country) => country.code === currentCode); + if (currentIndex === -1) return filteredCountries[0]?.code ?? null; - return countries[currentIndex + direction]?.code ?? currentCode; + return filteredCountries[currentIndex + direction]?.code ?? currentCode; }); } @@ -40,9 +56,15 @@ function App() {

Filters and selection

- {/* RegionFilter and SortSelector go here */} + {/* SortSelector goes here */} + navigateCountry(-1)} onNext={() => navigateCountry(1)} /> ) : ( -

No countries available.

+

+ {selectedRegion + ? 'No countries match the selected region.' + : 'No countries available.'} +

)} {/* FavoriteButton goes here */}
diff --git a/country-explorer/src/components/RegionFilter.css b/country-explorer/src/components/RegionFilter.css new file mode 100644 index 0000000..a3aea2f --- /dev/null +++ b/country-explorer/src/components/RegionFilter.css @@ -0,0 +1,24 @@ +.region-filter { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: center; + gap: 0.5rem; + margin: 1rem; +} + +.region-filter select { + min-width: 0; + max-width: 100%; + padding: 0.5rem; + font: inherit; + color: var(--text-h); + background: var(--bg); + border: 1px solid var(--border); + border-radius: 4px; +} + +.region-filter select:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 3px; +} diff --git a/country-explorer/src/components/RegionFilter.tsx b/country-explorer/src/components/RegionFilter.tsx new file mode 100644 index 0000000..9f29a28 --- /dev/null +++ b/country-explorer/src/components/RegionFilter.tsx @@ -0,0 +1,39 @@ +import { useId } from 'react'; +import './RegionFilter.css'; + +interface RegionFilterProps { + regions: string[]; + selectedRegion: string; + onRegionChange: (region: string) => void; + disabled?: boolean; +} + +function RegionFilter({ + regions, + selectedRegion, + onRegionChange, + disabled = false, +}: RegionFilterProps) { + const selectId = useId(); + + return ( +
+ + +
+ ); +} + +export default RegionFilter;