React TailwindCSS Tutoriel
5 min de lecture

Créer un custom Country selector avec Headless UI (React)

mckenziearts

Arthur Monney

@mckenziearts

Créer un custom Country selector avec Headless UI (React)

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

1composer 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

1php artisan migrate
2# ensuite
3php 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

1# npm
2npm install @headlessui/react
3 
4# Yarn
5yarn 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

1npm 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

1# RegisteredUserController
2 
3use App\Models\Country;
4use Inertia\Inertia;
5 
6class RegisteredUserController extends Controller
7{
8 /**
9 * Display the registration view.
10 *
11 * @return \Inertia\Response
12 */
13 public function create()
14 {
15 return Inertia::render('Auth/Register', [
16 'countries' => Country::all(),
17 ]);
18 }
19}

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.

1# La page complète React
2 
3import React, { useEffect, useState, Fragment } from 'react'
4import { Head, Link, useForm, usePage } from '@inertiajs/inertia-react'
5import { CheckIcon, SelectorIcon } from '@heroicons/react/solid'
6import { Combobox, Transition } from '@headlessui/react'
7import ReactCountryFlag from 'react-country-flag'
8 
9export default function Register() {
10 const { countries } = usePage().props
11 
12 const [selectedCountry, setSelectedCountry] = useState(countries[0])
13 const [query, setQuery] = useState('')
14 
15 const filteredCountries =
16 query === ''
17 ? countries
18 : countries.filter((country) =>
19 country.name
20 .toLowerCase()
21 .replace(/\s+/g, '')
22 .includes(query.toLowerCase().replace(/\s+/g, ''))
23 )
24 
25 return (
26 <div className="sm:col-span-3">
27 <Label forInput="country" value="Country" />
28 <Combobox value={selectedCountry} onChange={setSelectedCountry}>
29 <div className="relative mt-1">
30 <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">
31 <Combobox.Input
32 className="w-full border-none focus:ring-0 py-2 pl-3 pr-10 text-sm leading-5 text-gray-900"
33 displayValue={(country) => country.name}
34 onChange={(event) => setQuery(event.target.value)}
35 />
36 <Combobox.Button className="absolute inset-y-0 right-0 flex items-center pr-2">
37 <SelectorIcon className="w-5 h-5 text-gray-400" aria-hidden="true"/>
38 </Combobox.Button>
39 </div>
40 <Transition
41 as={Fragment}
42 leave="transition ease-in duration-100"
43 leaveFrom="opacity-100"
44 leaveTo="opacity-0"
45 afterLeave={() => setQuery('')}
46 >
47 <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">
48 {filteredCountries.length === 0 && query !== '' ? (
49 <div className="cursor-default select-none relative py-2 px-4 text-gray-700">
50 Nothing found.
51 </div>
52 ) : (
53 filteredCountries.map((country) => (
54 <Combobox.Option
55 key={country.id}
56 className={({ active }) =>
57 `cursor-default select-none relative py-2 pl-10 pr-4 ${
58 active ? 'text-white bg-green-600' : 'text-gray-900'
59 }`
60 }
61 value={country}
62 >
63 {({ selected, active }) => (
64 <>
65 <span className="absolute inset-y-0 left-0 pl-3 flex items-center">
66 <ReactCountryFlag countryCode={country.iso_3166_2} svg />
67 </span>
68 <span className={`block truncate ${selected ? 'font-medium' : 'font-normal'}`}>
69 {country.name}
70 </span>
71 {selected ? (
72 <span
73 className={`absolute inset-y-0 right-0 flex items-center px-3 ${
74 active ? 'text-white' : 'text-green-600'
75 }`}
76 >
77 <CheckIcon className="w-5 h-5" aria-hidden="true" />
78 </span>
79 ) : null}
80 </>
81 )}
82 </Combobox.Option>
83 ))
84 )}
85 </Combobox.Options>
86 </Transition>
87 </div>
88 </Combobox>
89 </div>
90 )
91}

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.
1<Combobox.Input
2 className="w-full border-none focus:ring-0 py-2 pl-3 pr-10 text-sm leading-5 text-gray-900"
3 displayValue={(country) => country.name}
4 onChange={(event) => setQuery(event.target.value)}
5/>
  • 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
1<span className="absolute inset-y-0 left-0 pl-3 flex items-center">
2 <ReactCountryFlag countryCode={country.iso_3166_2} svg />
3</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

mckenziearts

Arthur Monney

@mckenziearts

Fullstack Designer - Laravel & React Developer. Laravel Cameroon Organizer @laravelcm | @shopperlabs

Vous aimez cet article ? Faite le savoir en partageant