Amélioration de l'interface

Dans le cadre du développement du module todd_poll, nous avons un système de contrôle des votes pour éviter qu'une personne vote plusieurs fois. Ce système repose sur la constitution d'une liste de votants. Sont fournis le nom, le prénom et l'email. Un code PIN de 4 chiffres est créé automatiquement. La liste des votants peut-être créée à la main en ajoutant les entrées une à une. Elle peut aussi être constituée via un fichier CSV.

Conception de l'interface

Nous avons besoin de gagner en espace car les onglets avec les icônes et les noms font que les onglets sont sur deux lignes. Nous mettons en place un autre desgin où seul le nom de l'onglet actif est affiché, les autres étant réduits à leur icône.

Nous mettons le texte de chaque onglet dans un <span class="tab-label"> et appliquont une classe parente personnalisée todd-nav-icons-only au groupe d'onglets pour cibler précisément ces composants.

Nous utilisons le sélecteur CSS relationnel : le :has(). Voici la logique séquentielle implémentée dans admin.css :

  1. L'état de base (Plié) : Par défaut, tous les <span class="tab-label"> ont un max-width: 0 et une opacity: 0. Associé à la règle overflow: hidden, le nom de l'onglet est invisible et écrasé spatialement. Seule l'icône reste visible. Une propriété CSS transition est déclarée pour que tout changement de taille soit animé avec fluidité au lieu d'être sec.

  2. L'onglet actif par défaut (Déplié) : La classe Bootstrap .nav-link.active indique l'onglet courant. Une règle CSS forte qui dit : "Si le lien a la classe active, alors force la max-width à 150px et opacity à 1". Le texte s'affiche.

  3. Le survol d'un onglet inactif : La règle .nav-link:not(.active):hover dit simplement : "Si on survole un onglet qui n'est pas actif, force l'affichage de son texte". L'onglet inactif s'étend. Mais il y a un problème : si l'inactif s'étend, l'actif est toujours étendu, ce qui casse l'esthétique et prend trop de place !

  4. La sélecteur relationel (:has()) : Nous utilisons :has(), un sélecteur relationnel.

.todd-nav-icons-only:has(.nav-link:not(.active):hover) .nav-link.active .tab-label

Le navigateur l'interprète comme suit : "Regarde tout le conteneur d'onglets. S'il continent (has) un onglet inactif qui est actuellement survolé (hover)... Alors va cibler spécifiquement l'onglet actif, et réduis sa taille à 0 ! "

Ainsi, lorsque la souris touche un onglet réduit, le navigateur détecte l'action via le conteneur parent, "plie" l'onglet actuellement ouvert, et "déplie" simultanément l'onglet survolé.

/* ------------------------------------------------------------------------
   COMPOSANT: TODD Icon-Only Tabs (Dynamic Hover)
   ------------------------------------------------------------------------ */

/* Force single line */
.todd-nav-icons-only {
    flex-wrap: nowrap !important;
    overflow: hidden;
    gap: 0.25rem;
}

/* Base style for labels: Hidden and folded */
.todd-nav-icons-only .nav-link .tab-label {
    display: inline-block;
    max-width: 0;
    opacity: 0;
    overflow: hidden;
    vertical-align: middle;
    white-space: nowrap;
    transition: max-width 0.3s cubic-bezier(0.4, 0, 0.2, 1), 
                opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1), 
                margin 0.3s cubic-bezier(0.4, 0, 0.2, 1);
    margin-left: 0;
}

/* Fix icon alignment */
.todd-nav-icons-only .nav-link .icon {
    vertical-align: middle;
}

/* Improve Link Hitbox */
.todd-nav-icons-only .nav-link {
    display: flex;
    align-items: center;
    justify-content: center;
}

/* 1. Active Tab: Show label by default */
.todd-nav-icons-only .nav-link.active .tab-label {
    max-width: 150px;
    opacity: 1;
    margin-left: 0.5rem;
}

/* 2. Hovered Inactive Tab: Show its own label */
.todd-nav-icons-only .nav-link:not(.active):hover .tab-label {
    max-width: 150px;
    opacity: 1;
    margin-left: 0.5rem;
}

/* 3. The Magic: Hide the Active Tab's label when ANY Inactive Tab is hovered */
.todd-nav-icons-only:has(.nav-link:not(.active):hover) .nav-link.active .tab-label {
    max-width: 0 !important;
    opacity: 0 !important;
    margin-left: 0 !important;
}

/* 4. Fix Tab Borders: Transfer the active visual border state to the hovered tab */
.todd-nav-icons-only .nav-link:not(.active):hover {
    border-color: #dee2e6 #dee2e6 #fff !important;
}

/* 5. Hide the Active Tab's border when another tab is hovered */
.todd-nav-icons-only:has(.nav-link:not(.active):hover) .nav-link.active {
    border-color: transparent transparent transparent transparent !important;
}

L'interface avant :

Onglets AVANT

L'interface après :

Onglets APRÈS

Contrôle JSON

Nous avons la possibilité dans une interface de création/édition d'un module TODD d'avoir accès au contenu du payload JSON; Nous avons changé l'interface afin de mieux accéder à l'édition du JSON en dédiant l'entièreté de la modale à ce fichier et son édition. L'utilisateur a la possibilité de basculer entre le mode édition du JSON et le mode formulaire via un bouton dans l'en-tête du module.

La validité du JSON est vérifié en temps réel grâce un mécanisme d'écoute sur le champ d'input texte et à un try/catch sur la fonction JSON.parse() qui en cas de succès ou d'échec change la classe du badge :

// Validate JSON on type
$('#edit_payload').on('input', function () {
    try {
        let raw = $(this).val();
        if (raw.trim() !== '') {
            JSON.parse(raw);
            $('#json_valid_badge').removeClass('bg-danger').addClass('bg-success').text('JSON Valide');
        } else {
            $('#json_valid_badge').removeClass('bg-danger bg-success').text('');
        }
    } catch (e) {
        $('#json_valid_badge').removeClass('bg-success').addClass('bg-danger').text('JSON Invalide');
    }
});