contraindre le menu Bootstrap dans understrap de wp

pour que les dropdown du menu soient différents du fonctionnement par défaut de Bootstrap.
on veut que le clic sur le lien parent ouvre le lien
on veut que l’survol le lien enfant apparaisse

3 ficheir a crée :
un php ,
un css ,
un js

faire un thème enfant de UnderStrap
dans le dossier inc / refaire le fichier class-wp-bootstrap-navwalker.php
avec ce code

<?php

/**
 * WP Bootstrap Navwalker for Understrap - MODIFIÉ pour Lien Parent Cliquable
 *
 * @package WP-Bootstrap-Navwalker
 */

// Exit if accessed directly.
defined('ABSPATH') || exit;

/*
 * Class Name: Understrap_WP_Bootstrap_Navwalker
 * Description: Modification de la Navwalker pour permettre la navigation via le lien parent.
 * Version: 4.1.1
 */

/* Check if Class Exists. */
if (! class_exists('Understrap_WP_Bootstrap_Navwalker')) {
	/**
	 * Understrap_WP_Bootstrap_Navwalker class.
	 *
	 * @extends Walker_Nav_Menu
	 */
	class Understrap_WP_Bootstrap_Navwalker extends Walker_Nav_Menu
	{

		// ... (start_lvl() reste inchangé - OMITTED FOR BREVITY, ASSUME IT'S INCLUDED) ...
		public function start_lvl(&$output, $depth = 0, $args = array())
		{
			if (isset($args->item_spacing) && 'discard' === $args->item_spacing) {
				$t = '';
				$n = '';
			} else {
				$t = "\t";
				$n = "\n";
			}
			$indent = str_repeat($t, $depth);
			// Default class to add to the file.
			$classes = array('dropdown-menu');
			$class_names = join(' ', apply_filters('nav_menu_submenu_css_class', $classes, $args, $depth));
			$class_names = $class_names ? ' class="' . esc_attr($class_names) . '"' : '';

			$labelledby = '';
			preg_match_all('/(<a.*?id=\"|\')(.*?)\"|\'.*?>/im', $output, $matches);
			if (end($matches[2])) {
				$labelledby = 'aria-labelledby="' . end($matches[2]) . '"';
			}
			$output .= "{$n}{$indent}<ul$class_names $labelledby >{$n}";
		}
		// ... (Fin de start_lvl()) ...


		/**
		 * Starts the element output.
		 */
		public function start_el(&$output, $item, $depth = 0, $args = array(), $id = 0)
		{
			if (isset($args->item_spacing) && 'discard' === $args->item_spacing) {
				$t = '';
				$n = '';
			} else {
				$t = "\t";
				$n = "\n";
			}
			$indent = ($depth) ? str_repeat($t, $depth) : '';

			$classes = empty($item->classes) ? array() : (array) $item->classes;

			$linkmod_classes = array();
			$icon_classes    = array();

			$classes = self::separate_linkmods_and_icons_from_classes($classes, $linkmod_classes, $icon_classes, $depth);

			$icon_class_string = join(' ', $icon_classes);

			$args = apply_filters('nav_menu_item_args', $args, $item, $depth);

			// Add .dropdown or .active classes where they are needed.
			if (isset($args->has_children) && $args->has_children) {
				$classes[] = 'dropdown';
			}
			if (in_array('current-menu-item', $classes, true) || in_array('current-menu-parent', $classes, true)) {
				$classes[] = 'active';
			}

			// Add some additional default classes to the item.
			$classes[] = 'menu-item-' . $item->ID;
			$classes[] = 'nav-item';

			$classes = apply_filters('nav_menu_css_class', array_filter($classes), $item, $args, $depth);

			$class_names = join(' ', $classes);
			$class_names = $class_names ? ' class="' . esc_attr($class_names) . '"' : '';

			$id = apply_filters('nav_menu_item_id', 'menu-item-' . $item->ID, $item, $args, $depth);
			$id = $id ? ' id="' . esc_attr($id) . '"' : '';

			$output .= $indent . '<li itemscope="itemscope" itemtype="https://www.schema.org/SiteNavigationElement"' . $id . $class_names . '>';

			// initialize array for holding the $atts for the link item.
			$atts = array();

			// Set title from item to the $atts array - if title is empty then
			// default to item title.
			if (empty($item->attr_title)) {
				$atts['title'] = ! empty($item->title) ? strip_tags($item->title) : '';
			} else {
				$atts['title'] = $item->attr_title;
			}

			$atts['target'] = ! empty($item->target) ? $item->target : '';
			if ('_blank' === $item->target && empty($item->xfn)) {
				$atts['rel'] = 'noopener noreferrer';
			} else {
				$atts['rel'] = $item->xfn;
			}

			// Toujours utiliser l'URL de l'élément de menu.
			$atts['href'] = ! empty($item->url) ? $item->url : '#';

			// Si l'élément est dans un sous-menu.
			if ($depth > 0) {
				$atts['class'] = 'dropdown-item';
			} else {
				// Élément de menu de niveau supérieur
				$atts['class'] = 'nav-link';
			}

			// Si l'élément est un parent de niveau 0 (top-level) :
			if (isset($args->has_children) && $args->has_children && 0 === $depth && 1 !== $args->depth) {
				// On NE met PAS 'dropdown-toggle' sur le lien pour qu'il soit cliquable.
				// On s'assure que le lien a un ID pour que le sous-menu ait un 'aria-labelledby'.
				$atts['id'] = 'menu-item-link-' . $item->ID;
			}


			$atts['aria-current'] = $item->current ? 'page' : '';

			// update atts of this item based on any custom linkmod classes.
			$atts = self::update_atts_for_linkmod_type($atts, $linkmod_classes);
			// Allow filtering of the $atts array before using it.
			$atts = apply_filters('nav_menu_link_attributes', $atts, $item, $args, $depth);

			// Build a string of html containing all the atts for the item.
			$attributes = '';
			foreach ($atts as $attr => $value) {
				if (! empty($value)) {
					$value       = ('href' === $attr) ? esc_url($value) : esc_attr($value);
					$attributes .= ' ' . $attr . '="' . $value . '"';
				}
			}

			$linkmod_type = self::get_linkmod_type($linkmod_classes);

			/**
			 * START appending the internal item contents to the output.
			 */
			$item_output = isset($args->before) ? $args->before : '';

			if ('' !== $linkmod_type) {
				// is linkmod, output the required element opener.
				$item_output .= self::linkmod_element_open($linkmod_type, $attributes);
			} else {
				// Standard <a> tag.
				$item_output .= '<a' . $attributes . '>';
			}

			$icon_html = '';
			if (! empty($icon_class_string)) {
				$icon_html = '<i class="' . esc_attr($icon_class_string) . '" aria-hidden="true"></i> ';
			}

			$title = apply_filters('the_title', $item->title, $item->ID);

			$title = apply_filters('nav_menu_item_title', $title, $item, $args, $depth);

			if (in_array('sr-only', $linkmod_classes, true)) {
				$title         = self::wrap_for_screen_reader($title);
				$keys_to_unset = array_keys($linkmod_classes, 'sr-only', true);
				foreach ($keys_to_unset as $k) {
					unset($linkmod_classes[$k]);
				}
			}

			// Put the item contents into $output.
			$item_output .= isset($args->link_before) ? $args->link_before . $icon_html . $title . $args->link_after : '';

			if ('' !== $linkmod_type) {
				// is linkmod, output the required element opener.
				$item_output .= self::linkmod_element_close($linkmod_type);
			} else {
				// Standard <a> tag close.
				$item_output .= '</a>';
			}

			// Si l'élément est un parent de niveau 0, on ajoute le bouton de bascule APRÈS le lien.
			if (isset($args->has_children) && $args->has_children && 0 === $depth && 1 !== $args->depth) {
				// Ce SPAN sera le chevron séparé. On utilise l'ID du lien comme labelledby.
				$link_id = 'menu-item-link-' . $item->ID;

				$item_output .= '<span class="dropdown-toggle-split" 
									data-toggle="dropdown" 
									data-bs-toggle="dropdown" 
									aria-haspopup="true" 
									aria-expanded="false" 
									aria-labelledby="' . esc_attr($link_id) . '"
									title="' . esc_attr__('Ouvrir le sous-menu', 'understrap') . '"
								>
								</span>';
			}

			$item_output .= isset($args->after) ? $args->after : '';

			/**
			 * END appending the internal item contents to the output.
			 */
			$output .= apply_filters('walker_nav_menu_start_el', $item_output, $item, $depth, $args);
		}

		// ... (Le reste des méthodes (display_element, fallback, separate_linkmods_and_icons_from_classes, get_linkmod_type, update_atts_for_linkmod_type, wrap_for_screen_reader, linkmod_element_open, linkmod_element_close) restent inchangées - OMITTED FOR BREVITY, ASSUME THEY'RE INCLUDED) ...

		/**
		 * Traverse elements to create list from elements.
		 */
		public function display_element($element, &$children_elements, $max_depth, $depth, $args, &$output)
		{
			if (! $element) {
				return;
			}
			$id_field = $this->db_fields['id'];
			if (is_object($args[0])) {
				$args[0]->has_children = ! empty($children_elements[$element->$id_field]);
			}
			parent::display_element($element, $children_elements, $max_depth, $depth, $args, $output);
		}

		/**
		 * Menu Fallback
		 */
		public static function fallback($args)
		{
			if (current_user_can('edit_theme_options')) {

				/* Get Arguments. */
				$container       = $args['container'];
				$container_id    = $args['container_id'];
				$container_class = $args['container_class'];
				$menu_class      = $args['menu_class'];
				$menu_id         = $args['menu_id'];

				// initialize var to store fallback html.
				$fallback_output = '';

				if ($container) {
					$fallback_output .= '<' . esc_attr($container);
					if ($container_id) {
						$fallback_output .= ' id="' . esc_attr($container_id) . '"';
					}
					if ($container_class) {
						$fallback_output .= ' class="' . esc_attr($container_class) . '"';
					}
					$fallback_output .= '>';
				}
				$fallback_output .= '<ul';
				if ($menu_id) {
					$fallback_output .= ' id="' . esc_attr($menu_id) . '"';
				}
				if ($menu_class) {
					$fallback_output .= ' class="' . esc_attr($menu_class) . '"';
				}
				$fallback_output .= '>';
				$fallback_output .= '<li><a href="' . esc_url(admin_url('nav-menus.php')) . '" title="' . esc_attr__('Add a menu', 'understrap') . '">' . esc_html__('Add a menu', 'understrap') . '</a></li>';
				$fallback_output .= '</ul>';
				if ($container) {
					$fallback_output .= '</' . esc_attr($container) . '>';
				}

				// if $args has 'echo' key and it's true echo, otherwise return.
				if (array_key_exists('echo', $args) && $args['echo']) {
					echo $fallback_output; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
				} else {
					return $fallback_output;
				}
			}
		}

		/**
		 * Find any custom linkmod or icon classes and store in their holder
		 * arrays then remove them from the main classes array.
		 */
		private function separate_linkmods_and_icons_from_classes($classes, &$linkmod_classes, &$icon_classes, $depth)
		{
			// Loop through $classes array to find linkmod or icon classes.
			foreach ($classes as $key => $class) {
				// If any special classes are found, store the class in it's
				// holder array and and unset the item from $classes.
				if (preg_match('/^disabled|^sr-only/i', $class)) {
					// Test for .disabled or .sr-only classes.
					$linkmod_classes[] = $class;
					unset($classes[$key]);
				} elseif (preg_match('/^dropdown-header|^dropdown-divider|^dropdown-item-text/i', $class) && $depth > 0) {
					// Test for .dropdown-header or .dropdown-divider and a
					// depth greater than 0 - IE inside a dropdown.
					$linkmod_classes[] = $class;
					unset($classes[$key]);
				} elseif (preg_match('/^fa-(\S*)?|^fa(s|r|l|b)?(\s?)?$/i', $class)) {
					// Font Awesome.
					$icon_classes[] = $class;
					unset($classes[$key]);
				} elseif (preg_match('/^glyphicon-(\S*)?|^glyphicon(\s?)$/i', $class)) {
					// Glyphicons.
					$icon_classes[] = $class;
					unset($classes[$key]);
				}
			}

			return $classes;
		}

		/**
		 * Return a string containing a linkmod type and update $atts array
		 * accordingly depending on the decided.
		 */
		private function get_linkmod_type($linkmod_classes = array())
		{
			$linkmod_type = '';
			// Loop through array of linkmod classes to handle their $atts.
			if (! empty($linkmod_classes)) {
				foreach ($linkmod_classes as $link_class) {
					if (! empty($link_class)) {

						// check for special class types and set a flag for them.
						if ('dropdown-header' === $link_class) {
							$linkmod_type = 'dropdown-header';
						} elseif ('dropdown-divider' === $link_class) {
							$linkmod_type = 'dropdown-divider';
						} elseif ('dropdown-item-text' === $link_class) {
							$linkmod_type = 'dropdown-item-text';
						}
					}
				}
			}
			return $linkmod_type;
		}

		/**
		 * Update the attributes of a nav item depending on the limkmod classes.
		 */
		private function update_atts_for_linkmod_type($atts = array(), $linkmod_classes = array())
		{
			if (! empty($linkmod_classes)) {
				foreach ($linkmod_classes as $link_class) {
					if (! empty($link_class)) {
						// update $atts with a space and the extra classname...
						// so long as it's not a sr-only class.
						if ('sr-only' !== $link_class) {
							$atts['class'] .= ' ' . esc_attr($link_class);
						}
						// check for special class types we need additional handling for.
						if ('disabled' === $link_class) {
							// Convert link to '#' and unset open targets.
							$atts['href'] = '#';
							unset($atts['target']);
						} elseif ('dropdown-header' === $link_class || 'dropdown-divider' === $link_class || 'dropdown-item-text' === $link_class) {
							// Store a type flag and unset href and target.
							unset($atts['href']);
							unset($atts['target']);
						}
					}
				}
			}
			return $atts;
		}

		/**
		 * Wraps the passed text in a screen reader only class.
		 */
		private function wrap_for_screen_reader($text = '')
		{
			if ($text) {
				$text = '<span class="screen-reader-text">' . $text . '</span>';
			}
			return $text;
		}

		/**
		 * Returns the correct opening element and attributes for a linkmod.
		 */
		private function linkmod_element_open($linkmod_type, $attributes = '')
		{
			$output = '';
			if ('dropdown-item-text' === $linkmod_type) {
				$output .= '<span class="dropdown-item-text"' . $attributes . '>';
			} elseif ('dropdown-header' === $linkmod_type) {
				// For a header use a span with the .h6 class instead of a real
				// header tag so that it doesn't confuse screen readers.
				$output .= '<span class="dropdown-header h6"' . $attributes . '>';
			} elseif ('dropdown-divider' === $linkmod_type) {
				// this is a divider.
				$output .= '<div class="dropdown-divider"' . $attributes . '>';
			}
			return $output;
		}

		/**
		 * Return the correct closing tag for the linkmod element.
		 */
		private function linkmod_element_close($linkmod_type)
		{
			$output = '';
			if ('dropdown-header' === $linkmod_type || 'dropdown-item-text' === $linkmod_type) {
				// For a header use a span with the .h6 class instead of a real
				// header tag so that it doesn't confuse screen readers.
				$output .= '</span>';
			} elseif ('dropdown-divider' === $linkmod_type) {
				// this is a divider.
				$output .= '</div>';
			}
			return $output;
		}
	}
}

puis dans un fichier CSS ajouter ceci

/*
 * Correction de la superposition du menu Understrap
 * Pour séparer le lien cliquable du chevron d'ouverture du sous-menu.
 */

/* 1. Assurez-vous que le LI est le conteneur de positionnement */
.navbar-nav .dropdown.nav-item {
    position: relative; /* Clé pour le positionnement absolu du SPAN */
}

/* 2. Positionnement et style du Lien (A) */
.navbar-nav .dropdown.nav-item > a.nav-link {
    /* Le lien principal prend de la place mais laisse un peu de marge pour le chevron */
    padding-right: 35px !important; /* Augmenter le padding pour laisser de la place au toggle */
    display: inline-block;
    width: 100%; /* Le lien prend toute la largeur disponible (moins le padding) */
}

/* 3. Positionnement et style du SPAN (le Chevron de bascule) */
.navbar-nav .dropdown.nav-item > .dropdown-toggle-split {
    /* Rendre le SPAN absolu pour qu'il soit positionné par rapport au LI */
    position: absolute;
    top: 0;
    bottom: 0;
    right: 0;
    padding: 8px 10px; /* Adaptez le padding pour le rendre cliquable */
    
    /* Centrer le contenu (le chevron) */
    display: flex;
    align-items: center;
    justify-content: center;
    
    cursor: pointer;
    z-index: 10; /* Assurez-vous qu'il est au-dessus du lien */
    
    /* Style du chevron lui-même (comme celui par défaut de Bootstrap) */
    width: auto; /* Laisser le contenu définir la largeur */
    height: 100%; /* Important pour la hauteur du clic */
}

/* Ajout du visuel du chevron (ou utilisez une icône FontAwesome si vous en avez) */
.navbar-nav .dropdown.nav-item > .dropdown-toggle-split::after {
    display: inline-block;
    margin-left: 0.255em;
    vertical-align: 0.255em;
    content: "";
    border-top: 0.3em solid;
    border-right: 0.3em solid transparent;
    border-bottom: 0;
    border-left: 0.3em solid transparent;
}

/* 4. Correction des problèmes de survol/focus pour les liens parents */
.navbar-nav .dropdown.nav-item:hover > a.nav-link, 
.navbar-nav .dropdown.nav-item > a.nav-link:focus {
    /* Maintenir les styles de survol habituels */
    color: inherit; /* Ou la couleur de votre choix */
}

/* S'assurer que le sous-menu ne reste pas 'position: absolute' par rapport au LI */
.navbar-nav .dropdown.nav-item.show > .dropdown-menu {
    /* C'est géré par Bootstrap, mais on s'assure qu'il s'affiche correctement */
    display: block; 
}

puis dans un fichier JS ajouter ceci

/**
 * Script Understrap : Permet l'ouverture du sous-menu par :
 * 1. Clic sur le chevron (dropdown-toggle-split).
 * 2. Survol (hover) sur les écrans de bureau (>= 992px).
 */

jQuery(document).ready(function($) {

    var $dropdownParents = $('.navbar-nav .dropdown.nav-item');
    var $toggleSplit = $('.navbar-nav .dropdown-toggle-split');
    var desktopWidth = 992; // Taille de l'écran pour Bootstrap LG (desktop)

    // Fonction pour ouvrir/fermer le sous-menu
    function toggleDropdown($li, state) {
        if (state) {
            $li.addClass('show');
            $li.find('.dropdown-menu').first().addClass('show');
            $li.find('.dropdown-toggle-split').attr('aria-expanded', true);
        } else {
            $li.removeClass('show');
            $li.find('.dropdown-menu').first().removeClass('show');
            $li.find('.dropdown-toggle-split').attr('aria-expanded', false);
        }
    }

    // --- 1. Gestion du Clic (pour les mobiles et le chevron) ---

    // Écouteur d'événements pour le clic sur le SPAN de bascule
    $toggleSplit.on('click', function(e) {
        e.preventDefault(); 
        e.stopPropagation(); 
        
        var $parentLi = $(this).closest('.dropdown');
        var isExpanded = $parentLi.hasClass('show');

        // Fermer tous les autres menus ouverts
        $dropdownParents.not($parentLi).each(function() {
            toggleDropdown($(this), false);
        });

        // Basculer l'état du menu actuel
        toggleDropdown($parentLi, !isExpanded);
    });

    // Optionnel : Gérer la fermeture lorsque l'utilisateur clique en dehors du menu
    $(document).on('click', function(e) {
        if (!$(e.target).closest($dropdownParents).length) {
            toggleDropdown($dropdownParents, false);
        }
    });


    // --- 2. Gestion du Survol (Hover) pour les Grands Écrans ---
    
    // Vérifier la taille de l'écran avant d'activer le survol
    if (window.matchMedia('(min-width: ' + desktopWidth + 'px)').matches) {
        
        $dropdownParents.on({
            'mouseenter': function() {
                var $li = $(this);
                // Ouvrir uniquement si l'utilisateur ne maintient pas le clic
                if (!$li.hasClass('clicked-state')) {
                    // Fermer les autres menus avant d'ouvrir
                    $dropdownParents.not($li).each(function() {
                        toggleDropdown($(this), false);
                    });
                    toggleDropdown($li, true);
                }
            },
            'mouseleave': function() {
                var $li = $(this);
                // Fermer après un court délai pour permettre le déplacement vers le sous-menu
                setTimeout(function() {
                    // Fermer seulement si la souris n'est plus sur le LI parent ou le sous-menu
                    if (!$li.is(':hover')) {
                        toggleDropdown($li, false);
                    }
                }, 100); // Délai de 100ms
            }
        });
        
        // Empêcher l'ouverture/fermeture par survol si l'utilisateur clique (comportement mobile)
        $dropdownParents.find('a').on('click', function() {
             // Clic sur le lien, on laisse le comportement de navigation se produire.
             toggleDropdown($dropdownParents, false); // Ferme les autres menus
        });
    }

});

Publications similaires