Personnaliser le design des radio-button

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 :

Personnaliser le design des radio-boutons#1 - La page avec les boutons à transformer
Personnaliser le design des radio-boutons#1 – 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.

Personnaliser le design des radio-boutons#2 - Les boutons à transformer
Personnaliser le design des radio-boutons#2 – Les boutons à transformer

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 :

Personnaliser le design des radio-boutons#3 - Les boutons sont ici des labels reliés au radio-button
Personnaliser le design des radio-boutons#3 – Les boutons sont ici des labels reliés au 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 :

Personnaliser le design des radio-boutons#4 - Le composant avec les radio boutons personnalisés
Personnaliser le design des radio-boutons#4 – Le composant avec les radio boutons personnalisés

Les onglets sont soulignés dès que nous en choisissons un :

Personnaliser le design des radio-boutons#5 - Le composant avec les radio boutons personnalisés avec sélection de l'onglet
Personnaliser le design des radio-boutons#5 – Le composant avec les radio boutons personnalisés avec sélection de l’onglet

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>
Si vous avez aimé l'article vous êtes libre de le partager :-)

Laisser un commentaire