Nous souhaitons personnaliser le design des radio-button, afin de créer un visuel de tabulation sous forme de composant livecomponent. Nous reprenons le code HTML Tailwind de la boite de recherche.
Ce bloc de recherche est présent dans la page que nous avons codé avec GPT-4.
Transformation du code html Tailwind
La page avec les boutons à transformer :
La version initiale du code est la suivante :
<!DOCTYPE html> <html lang="fr"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Bloc de recherche</title> <script src="https://cdn.tailwindcss.com"></script> <link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600&display=swap" rel="stylesheet"> </head> <body class="bg-gray-100 flex justify-center"> <div id="encadrement" class="flex flex-col justify-between w-full md:w-[489px] h-auto md:h-[663px] border border-gray-200 bg-white mx-auto mt-4"> <div> <div id="search-block" class="flex flex-col items-center justify-center mt-4 pl-6"> <div id="title-section" class="w-full"> <p class="text-sm uppercase tracking-wide text-gray-600 mb-6">Looking for something special?</p> <h1 class="text-4xl font-semibold my-2 mb-8">Find your perfect home near the water with the Waterside Network...</h1> <div class="w-16 py-2"> <img src="https://placehold.co/64x16" alt="Decorative squiggly line"> </div> </div> </div> </div> <div id="tabs-and-search" class="w-full border border-gray-200"> <div id="tabs-container" class="w-full"> <div class="flex items-center pl-6"> <div class="border-l border-gray-200 h-10"></div> <button class="tab px-6 py-2 text-gray-600 uppercase tracking-wide border-b-4 border-yellow-400" data-active="true">For Sale</button> <div class="border-l border-gray-200 h-10"></div> <button class="tab text-gray-600 uppercase tracking-wide px-6 py-2">To Rent</button> <div class="border-l border-gray-200 h-10"></div> </div> </div> <div id="search-container" class="bg-blue-50 p-6 md:pt-6 md:pb-20 w-full flex flex-col space-y-4 md:space-y-10"> <label for="search" class="text-sm font-semibold text-gray-600 uppercase tracking-wide">Where...</label> <input id="search" type="text" placeholder="Anywhere Near The Water" class="px-8 py-4 border border-gray-300"> <div class="flex md:justify-end"> <button class="bg-gray-800 text-white uppercase tracking-wide px-8 py-4">Search</button> </div> </div> </div> </div> </body> </html>
La partie qui nous concerne commence en ligne 23, et nous n’avons pas besoin de la partie search-container.
<!DOCTYPE html> <html lang="fr"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Bloc de recherche</title> <script src="https://cdn.tailwindcss.com"></script> <link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600&display=swap" rel="stylesheet"> </head> <body class="bg-gray-100 flex justify-center"> <div id="encadrement" class="flex flex-col justify-between w-full md:w-[489px] h-auto md:h-[663px] border border-gray-200 bg-white mx-auto mt-4"> <div id="tabs-and-search" class="w-full border border-gray-200"> <div id="tabs-container" class="w-full"> <div class="flex items-center pl-6"> <div class="border-l border-gray-200 h-10"></div> <button class="tab px-6 py-2 text-gray-600 uppercase tracking-wide border-b-4 border-yellow-400" data-active="true">For Sale</button> <div class="border-l border-gray-200 h-10"></div> <button class="tab text-gray-600 uppercase tracking-wide px-6 py-2">To Rent</button> <div class="border-l border-gray-200 h-10"></div> </div> </div> </div> </div> </body> </html>
Nous mettons ce code dans CodePen afin de bénéficier de la visualisation du résultat au fur et à mesure des transformations.
Dans le code, les bouton For Sale et To Rent sont actuellement en html <button>. Nous allons utiliser la transformation de ces boutons et utiliser la propriété des radio-button décrite dans cette vidéo qui explique comment faire des radio-button qui ressemble à des boutons.
Pour créer des radio-button personnalisés nous remplaçons les buttons par des labels. Le visuel ne change pas. Nous ajoutons devant chaque label un input radio en hidden
<input type="radio" value="ForSale" id="ForSale" name="ChoixDemande" class="hidden"/> <input type="radio" value="ToRent" id="ToRent" name="ChoixDemande" class="hidden"/>
Le visuel ne change pas. Nous mettons maintenant le lien entre le label et le input correspondant avec la propriété for= »ForSale » et for= »ToRent ».
A cette étape le code est le suivant :
<!DOCTYPE html> <html lang="fr"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Bloc de recherche</title> <script src="https://cdn.tailwindcss.com"></script> <link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600&display=swap" rel="stylesheet"> </head> <body class="bg-gray-100 flex justify-center"> <div id="encadrement" class="flex flex-col justify-between w-full md:w-[489px] h-auto md:h-[663px] border border-gray-200 bg-white mx-auto mt-4"> <div id="tabs-and-search" class="w-full border border-gray-200"> <div id="tabs-container" class="w-full"> <div class="flex items-center pl-6"> <div class="border-l border-gray-200 h-10"></div> <input type="radio" value="ForSale" id="ForSale" name="ChoixDemande" class="hidden"/> <label for="ForSale" class="tab px-6 py-2 text-gray-600 uppercase tracking-wide border-b-4 border-yellow-400" data-active="true">For Sale</label> <div class="border-l border-gray-200 h-10"></div> <input type="radio" value="ToRent" id="ToRent" name="ChoixDemande" class="hidden"/> <label for="ToRent" class="tab text-gray-600 uppercase tracking-wide px-6 py-2">To Rent</label> <div class="border-l border-gray-200 h-10"></div> </div> </div> </div> </div> </body> </html>
Pour rendre actif le choix du radio bouton nous ajoutons peer dans la class de l’input radio-button et nous mettons le selecteur peer-checked: devant border-b-4 et border-yellow-400. Nous devons grouper le label et l’input dans un même div.
Le code ci-dessous fonctionne.
<!DOCTYPE html> <html lang="fr"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Bloc de recherche</title> <script src="https://cdn.tailwindcss.com"></script> <link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600&display=swap" rel="stylesheet"> </head> <body class="bg-gray-100 flex justify-center"> <div id="encadrement" class="flex flex-col justify-between w-full md:w-[489px] h-auto md:h-[663px] border border-gray-200 bg-white mx-auto mt-4"> <div id="tabs-and-search" class="w-full border border-gray-200"> <div id="tabs-container" class="w-full"> <div class="flex items-center pl-6"> <div class="border-l border-gray-200 h-10"></div> <div> <input type="radio" value="ForSale" id="ForSale" name="ChoixDemande" class="hidden peer"/> <label for="ForSale" class="tab px-6 py-2 text-gray-600 uppercase tracking-wide peer-checked:border-b-4 peer-checked:border-yellow-400" data-active="true">For Sale</label> </div> <div class="border-l border-gray-200 h-10"></div> <div> <input type="radio" value="ToRent" id="ToRent" name="ChoixDemande" class="hidden peer"/> <label for="ToRent" class="tab px-6 py-2 text-gray-600 uppercase tracking-wide peer-checked:border-b-4 peer-checked:border-yellow-400">To Rent</label> </div> <div class="border-l border-gray-200 h-10"></div> </div> </div> </div> </div> </body> </html>
La solution finale pour le bouton personnalisé :
<!DOCTYPE html> <html lang="fr"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Bloc de recherche</title> <script src="https://cdn.tailwindcss.com"></script> <link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600&display=swap" rel="stylesheet"> </head> <body class="bg-gray-100 flex justify-center"> <div id="encadrement" class="flex flex-col justify-between w-full md:w-[489px] h-auto md:h-[663px] border border-gray-200 bg-white mx-auto mt-4"> <div id="tabs-and-search" class="w-full border border-gray-200"> <div id="tabs-container" class="w-full"> <div class="flex items-center pl-6"> <div class="border-l border-gray-200 h-10"></div> <!-- premier bouton --> <div> <input checked type="radio" value="ForSale" id="ForSale" name="ChoixDemande" class="hidden peer"/> <label for="ForSale" class="tab px-6 py-2 text-gray-600 uppercase tracking-wide peer-checked:border-b-4 peer-checked:border-yellow-400" data-active="true">For Sale</label> </div> <div class="border-l border-gray-200 h-10"></div> <!-- deuxieme bouton --> <div> <input type="radio" value="ToRent" id="ToRent" name="ChoixDemande" class="hidden peer"/> <label for="ToRent" class="tab px-6 py-2 text-gray-600 uppercase tracking-wide peer-checked:border-b-4 peer-checked:border-yellow-400">To Rent</label> </div> <div class="border-l border-gray-200 h-10"></div> </div> </div> </div> </div> </body> </html>
Le visuel sur le deuxième bouton. Le changement est controlé par le radio-button :
Création du livecomponent pour personnaliser le design des radio-button
Le code du composant, avec la possibilité de créer les boutons à l’appel du composant :
defmodule VotreAppWeb.RadioButtonComponent do use VotreAppWeb, :live_component def render(assigns) do ~H""" <div id="encadrement" class="flex flex-col justify-between w-full md:w-[489px] h-auto md:h-[663px] border border-gray-200 bg-white mx-auto mt-4"> <div id="tabs-and-search" class="w-full border border-gray-200"> <div id="tabs-container" class="w-full"> <div class="flex items-center pl-6"> <div class="border-l border-gray-200 h-10"></div> <%= for item <- @items do %> <!-- bouton généré dynamiquement --> <div> <input type="radio" value={item.id} id={item.id} name="ChoixDemande" class="hidden peer" phx-click="update" phx-value-type={item.id}/> <label for={item.id} class="tab px-6 py-2 text-gray-600 uppercase tracking-wide peer-checked:border-b-4 peer-checked:border-yellow-400">{item.caption}</label> </div> <div class="border-l border-gray-200 h-10"></div> <% end %> </div> </div> </div> </div> """ end def handle_event("update", %{"type" => type}, socket) do {:noreply, assign(socket, :selected_type, type)} end end
La fonction handle_event mettra à jour l’assign :selected_type
avec l’identifiant du bouton radio sélectionné.
Utilisation du livecomponent :
<.live_component module={VotreAppWeb.RadioButtonComponent} id="radio_button_1" items={[ %{caption: "For Sale", id: "ForSale"}, %{caption: "To Rent", id: "ToRent"} ]} />
Nous établissons la liste des différents boutons à la création du composant.
Vérification du composant créé pour personnaliser le design des radio-button
Ajoutons notre composant dans le projet Catalog créé pour tester notre composant menu.
- cd C:\CarbonX1\Phoenix\Projets\catalog
- code .
- mix compile
- mix phx.new catalog
- http://localhost:4000/navbar
Nous ajoutons le fichier radiogroup_component.ex dans le dossier :
- CATALOG/lib/catalog_web/livecomponents
Utiliser la commande dmod dans le fichier radiogroup_component.ex permet d’ajouter le module du composant :
defmodule CatalogWeb.Livecomponents.RadiogroupComponent do end
Copions-codons le code créé par Perplexity IA. Notez la ligne 2 : use CatalogWeb, :live_component.
defmodule CatalogWeb.Livecomponents.RadiogroupComponent do use CatalogWeb, :live_component def render(assigns) do ~H""" <div id="encadrement" class="flex flex-col justify-between w-full md:w-[489px] h-auto md:h-[663px] border border-gray-200 bg-white mx-auto mt-4"> <div id="tabs-and-search" class="w-full border border-gray-200"> <div id="tabs-container" class="w-full"> <div class="flex items-center pl-6"> <div class="border-l border-gray-200 h-10"></div> <%= for item <- @items do %> <!-- bouton généré dynamiquement --> <div> <input type="radio" value={item.id} id={item.id} name="ChoixDemande" class="hidden peer" phx-click="update" phx-value-type={item.id}/> <label for={item.id} class="tab px-6 py-2 text-gray-600 uppercase tracking-wide peer-checked:border-b-4 peer-checked:border-yellow-400">{item.caption}</label> </div> <div class="border-l border-gray-200 h-10"></div> <% end %> </div> </div> </div> </div> """ end def handle_event("update", %{"type" => type}, socket) do {:noreply, assign(socket, :selected_type, type)} end end
Mettons notre composant dans la page navbar_live.ex.
<.live_component module={CatalogWeb.Livecomponents.RadiogroupComponent} id="radio_group" items={[ %{caption: "For Sale", id: "ForSale"}, %{caption: "To Rent", id: "ToRent"} ]} />
Lançons la compilation puis l’exécution. Nous avons l’erreur
- ** (UndefinedFunctionError) function CatalogWeb.NavbarLive.handle_event/3 is undefined or private
Nous avons 2 options :
- mettre la méthode handle dans la page navbar_live
- ajouter dans l’input radio phx-target={@myself} à côté de phx-click (ligne 14)
Une autre erreur est présente dans le code (ligne 15). Nous devons corriger :
- <label>{item.caption}</label> par
- <label><%= item.caption %></label>
Note :
- dans un tag, l’appel se fait par : {item.caption}
- en dehors des tags, l’appel se fait par : <%= item.caption %>
Le composant dans sa version fonctionnelle :
defmodule CatalogWeb.Livecomponents.RadiogroupComponent do use CatalogWeb, :live_component def render(assigns) do ~H""" <div id="encadrement" class="flex flex-col justify-between w-full md:w-[489px] h-auto md:h-[663px] border border-gray-200 bg-white mx-auto mt-4"> <div id="tabs-and-search" class="w-full border border-gray-200"> <div id="tabs-container" class="w-full"> <div class="flex items-center pl-6"> <div class="border-l border-gray-200 h-10"></div> <%= for item <- @items do %> <!-- bouton généré dynamiquement --> <div> <input type="radio" value={item.id} id={item.id} name="ChoixDemande" class="hidden peer" phx-click="update" phx-value-type={item.id} phx-target={@myself}/> <label for={item.id} class="tab px-6 py-2 text-gray-600 uppercase tracking-wide peer-checked:border-b-4 peer-checked:border-yellow-400"><%= item.caption %></label> </div> <div class="border-l border-gray-200 h-10"></div> <% end %> </div> </div> </div> </div> """ end def handle_event("update", %{"type" => type}, socket) do {:noreply, assign(socket, :selected_type, type)} end end
Visuel du composant dans la page navbar_live.ex au chargement de la page :
Les onglets sont soulignés dès que nous en choisissons un :
Amélioration du composant créé pour personnaliser le design des radio-button
nous devons sortir les classes d’habillage du radio-group afin de pouvoir l’utiliser plus simplement. L’idée étant de ne garder que les boutons, sans le cadre autour. Le cadre étant ajouté dans la page qui utilisera le composant.
Le code du composant :
defmodule CatalogWeb.Livecomponents.RadiogroupComponent do use CatalogWeb, :live_component def render(assigns) do ~H""" <div class="flex items-center"> <div class="border-l border-gray-200 h-10"></div> <%= for item <- @items do %> <!-- bouton généré dynamiquement --> <div> <input type="radio" value={item.id} id={item.id} name="ChoixDemande" class="hidden peer" phx-click="update" phx-value-type={item.id} phx-target={@myself}/> <label for={item.id} class="tab px-6 py-2 text-gray-600 uppercase tracking-wide peer-checked:border-b-4 peer-checked:border-yellow-400"><%= item.caption %></label> </div> <div class="border-l border-gray-200 h-10"></div> <% end %> </div> """ end def handle_event("update", %{"type" => type}, socket) do {:noreply, assign(socket, :selected_type, type)} end end
Son utilisation dans la page :
defmodule CatalogWeb.NavbarLive do @moduledoc """ Ce module montre l'affichage de la barre de menu qui est dans app.html.heex menu_items est définis ici dans mount et donne la description des items à afficher dans le menu """ use CatalogWeb, :live_view def mount(_params, _session, socket) do menu_items = [ %{caption: "BUY", link: "#", border: false, desk_separator: true}, %{caption: "SELL", link: "#", border: false, desk_separator: true}, %{caption: "RENT", link: "#", border: false, desk_separator: true}, %{caption: "HOLIDAY LETS", link: "#", border: false, desk_separator: true}, %{caption: "BOAT SALES", link: "#", border: false, desk_separator: true}, %{caption: "MOORINGS", link: "#", border: false, desk_separator: true}, %{caption: "THE NETWORK", link: "#", border: false, desk_separator: true}, %{caption: "JOIN THE NETWORK", link: "#", border: true, desk_separator: false} ] socket = assign(socket, items: menu_items, menu_visible: false) # IO.inspect(socket.assigns, label: "mount assigns NavBarLive") {:ok, socket} end def render(assigns) do # IO.inspect(assigns, label: "render assigns NavBarLive") ~H""" <div class="mt-4"> Bonjour </div> <div id="encadrement" class="flex flex-col justify-between w-full md:w-[489px] h-auto md:h-[663px] border border-gray-200 bg-white mx-auto mt-4"> <div id="tabs-and-search" class="w-full border border-gray-200"> <div id="tabs-container" class="w-full"> <div class="pl-6"> <.live_component module={CatalogWeb.Livecomponents.RadiogroupComponent} id="radio_group" items={[ %{caption: "For Sale", id: "ForSale"}, %{caption: "To Rent", id: "ToRent"} ]} /> </div> </div> </div> </div> """ end end
Le décalage pl-6 est à mettre dans un div autour du composant (ligne 35). LE visuel n’a pas changé.
Nous allons simplifier le composant en gérant les bordures droite et gauche dans le div qui encadre chaque « bouton ».
La version avec gestion des bordures. Nous avons ajusté la hauteur du bouton avec h-8 pour que le soulignement de sélection soit juste au niveau du boutons :
defmodule CatalogWeb.Livecomponents.RadiogroupComponent do use CatalogWeb, :live_component def render(assigns) do ~H""" <div class="flex items-center"> <%= for item <- @items do %> <!-- bouton généré dynamiquement --> <div class="border-l border-gray-200 h-8"> <input type="radio" value={item.id} id={item.id} name="ChoixDemande" class="hidden peer" phx-click="update" phx-value-type={item.id} phx-target={@myself}/> <label for={item.id} class="tab px-6 py-2 text-gray-600 uppercase tracking-wide peer-checked:border-b-4 peer-checked:border-yellow-400"><%= item.caption %></label> </div> <% end %> <div class="border-l border-gray-200 h-8"></div> </div> """ end def handle_event("update", %{"type" => type}, socket) do {:noreply, assign(socket, :selected_type, type)} end end
On pourrait aussi mettre la couleur du soulignement en paramètre du composant.
Pour permettre de choisir la valeur sélectionnée par defaut, nous ajoutons :
- checked={if item.checked, do: true, else: nil}
avec l’utilisation du composant comme ceci :
<div class="pl-6"> <.live_component module={CatalogWeb.Livecomponents.RadiogroupComponent} id="radio_group" items={[ %{caption: "For Sale", id: "ForSale", checked: true}, %{caption: "To Rent", id: "ToRent", checked: false} ]} /> </div>
Le code complet est le suivant :
defmodule CatalogWeb.Livecomponents.RadiogroupComponent do use CatalogWeb, :live_component def render(assigns) do ~H""" <div class="flex items-center"> <%= for item <- @items do %> <!-- bouton généré dynamiquement --> <div class="border-l border-gray-200 h-8"> <input type="radio" checked={if item.checked, do: true, else: nil} value={item.id} id={item.id} name="ChoixDemande" class="hidden peer" phx-click="update" phx-value-type={item.id} phx-target={@myself}/> <label for={item.id} class="tab px-6 py-2 text-gray-600 uppercase tracking-wide peer-checked:border-b-4 peer-checked:border-yellow-400"><%= item.caption %></label> </div> <% end %> <div class="border-l border-gray-200 h-8"></div> </div> """ end def handle_event("update", %{"type" => type}, socket) do {:noreply, assign(socket, :selected_type, type)} end end
Conclusion
Nous avons un radio bouton personnalisé avec le design de l’application.
Les boutons dont nous avons besoin s’ajoutent dans l’appel du composant sous le format d’un tableau :
<div class="pl-6"> <.live_component module={CatalogWeb.Livecomponents.RadiogroupComponent} id="radio_group" items={[ %{caption: "For Sale", id: "ForSale", checked: true}, %{caption: "To Rent", id: "ToRent", checked: false} ]} /> </div>