Créer un custom Country selector avec Headless UI (React)
Arthur Monney
@mckenziearts
De plus en plus, quand nous développons nos applications, nous voulons offrir une meilleure expérience à nos utilisateurs, et ceci passe aussi par nos formulaires. Aujourd'hui nous allons voir un sujet qui fait souvent couler beaucoup d'encre chez les développeurs.
Comment personnaliser nos dropdowns pour les pays 😱. Oui faut reconnaître que utiliser l'input select offre juste le listing et ceci encore pour un utilisateur qui aime voir les images il va trouver ça très louche. Et il pourra se demander:
Mais pourquoi il y'a pas de drapeaux sur ces pays?
Et c'est assez naze de perdre des utilisateurs sur des choses aussi simple.Alors heureux pour vous dans cet article nous allons voir comment créer nous même notre propre select pour les pays. Et pour cela nous allons utiliser:
- Tailwind CSS
- Headless UI (React)
Commençons avec les pays
Eh Oui! Nous n'allons pas faire un dropdown de pays sans pays. Pour ce tutoriel je suis dans un projet Laravel (avec Inertia et React déjà disponible), mais vous pouvez utiliser n'importe quoi (Symfony, Ruby on Rails, API, etc) pour avoir une base de données de pays. Pour cet exemple j'ai trouvé un package appelé laravel-countries qui va juste nous donner une liste de tous les pays du monde (rien que ça 🤩)
Nous allons l'installer via composer
composer require bhuvidya/laravel-countries
Une fois que c'est fait nous allons exécuter la migration et le seeder pour remplir nos pays dans la base de données
php artisan migrate# ensuitephp artisan db:seed --class='Bhuvidya\Countries\CountriesSeeder'
Maintenant on peut passer à la suite.
Installation
Nous n'allons pas voir ici comment installer et configurer Tailwind css cela a déjà été fait dans cet article.
Headless UI est une librairie de composants d'interface utilisateur totalement dépourvus de style et entièrement accessibles, conçus pour s'intégrer parfaitement à Tailwind CSS. Nous allons l'installer dans notre projet
# npmnpm install @headlessui/react # Yarnyarn add @headlessui/react
Une fois que c'est fait vous n'avez besoin d'aucune configuration, tout fonctionnera d'un coup.
Maintenant pour finir nous allons installer une petite dépendance React qui va nous être très utile pour afficher nos drapeaux react-country-flag
npm install --save react-country-flag
Créons notre dropdown
Premièrement nous allons récupérer nos pays depuis notre base de données pour l'envoyer à notre vue react
# RegisteredUserController use App\Models\Country;use Inertia\Inertia; class RegisteredUserController extends Controller{ /** * Display the registration view. * * @return \Inertia\Response */ public function create() { return Inertia::render('Auth/Register', [ 'countries' => Country::all(), ]); }}
Cet exemple est fait avec Inertia parce que nous sommes sous une application React. Si vous voulez en savoir plus sur Inertia je vous recommande la documentation
Pour réaliser notre dropdown nous allons utiliser le composant Combobox (Autocomplete) de la libraire Headless UI.
# La page complète React import React, { useEffect, useState, Fragment } from 'react'import { Head, Link, useForm, usePage } from '@inertiajs/inertia-react'import { CheckIcon, SelectorIcon } from '@heroicons/react/solid'import { Combobox, Transition } from '@headlessui/react'import ReactCountryFlag from 'react-country-flag' export default function Register() { const { countries } = usePage().props const [selectedCountry, setSelectedCountry] = useState(countries[0]) const [query, setQuery] = useState('') const filteredCountries = query === '' ? countries : countries.filter((country) => country.name .toLowerCase() .replace(/\s+/g, '') .includes(query.toLowerCase().replace(/\s+/g, '')) ) return ( <div className="sm:col-span-3"> <Label forInput="country" value="Country" /> <Combobox value={selectedCountry} onChange={setSelectedCountry}> <div className="relative mt-1"> <div className="relative w-full text-left bg-white rounded-md border border-gray-300 cursor-default focus:outline-none focus-visible:ring-2 focus-visible:ring-opacity-75 focus-visible:ring-white focus-visible:ring-offset-green-300 focus-visible:ring-offset-2 sm:text-sm overflow-hidden"> <Combobox.Input className="w-full border-none focus:ring-0 py-2 pl-3 pr-10 text-sm leading-5 text-gray-900" displayValue={(country) => country.name} onChange={(event) => setQuery(event.target.value)} /> <Combobox.Button className="absolute inset-y-0 right-0 flex items-center pr-2"> <SelectorIcon className="w-5 h-5 text-gray-400" aria-hidden="true"/> </Combobox.Button> </div> <Transition as={Fragment} leave="transition ease-in duration-100" leaveFrom="opacity-100" leaveTo="opacity-0" afterLeave={() => setQuery('')} > <Combobox.Options className="absolute w-full py-1 mt-1 overflow-auto text-base bg-white rounded-md shadow-lg max-h-60 ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm"> {filteredCountries.length === 0 && query !== '' ? ( <div className="cursor-default select-none relative py-2 px-4 text-gray-700"> Nothing found. </div> ) : ( filteredCountries.map((country) => ( <Combobox.Option key={country.id} className={({ active }) => `cursor-default select-none relative py-2 pl-10 pr-4 ${ active ? 'text-white bg-green-600' : 'text-gray-900' }` } value={country} > {({ selected, active }) => ( <> <span className="absolute inset-y-0 left-0 pl-3 flex items-center"> <ReactCountryFlag countryCode={country.iso_3166_2} svg /> </span> <span className={`block truncate ${selected ? 'font-medium' : 'font-normal'}`}> {country.name} </span> {selected ? ( <span className={`absolute inset-y-0 right-0 flex items-center px-3 ${ active ? 'text-white' : 'text-green-600' }`} > <CheckIcon className="w-5 h-5" aria-hidden="true" /> </span> ) : null} </> )} </Combobox.Option> )) )} </Combobox.Options> </Transition> </div> </Combobox> </div> )}
Decomposont posement notre composant
- Input recherche: Le premier élément qui nous permet de saisir le texte et de réduire le nombre de résultats. C'est toujours mieux qu'un select classique ou nous devrions être en train de scroller pendant 15 minutes pour sélectionner la valeur souhaitée.
<Combobox.Input className="w-full border-none focus:ring-0 py-2 pl-3 pr-10 text-sm leading-5 text-gray-900" displayValue={(country) => country.name} onChange={(event) => setQuery(event.target.value)}/>
-
Affichage du Drapeau: Pour afficher le drapeau nous utilisons la librairie
react-country-flag
qui va à partir du code ISO du pays nous fournir le drapeau en svg. Ce qui nous évite de télécharger manuellement tous les drapeaux de tous les pays
<span className="absolute inset-y-0 left-0 pl-3 flex items-center"> <ReactCountryFlag countryCode={country.iso_3166_2} svg /></span>
La valeur country.iso_3166_2
représente un champ de ma table countries. Dans votre base de données ou votre API ce code peut avoir une autre appellation.
Le résultat final
Arthur Monney
@mckenziearts
Fullstack Designer - Laravel & React Developer. Laravel Cameroon Organizer @laravelcm | @shopperlabs