From b6235149d20daf52c48eef1f0b169069025808b5 Mon Sep 17 00:00:00 2001
From: gloas <gloas@afi-sa.fr>
Date: Wed, 7 Jul 2021 10:07:51 +0200
Subject: [PATCH] hotline MT #136539 menu widget use carousel layouts

---
 VERSIONS_HOTLINE/136539                       |   1 +
 library/Class/Systeme/Widget/Abstract.php     |  10 +
 library/Class/Systeme/Widget/Menu.php         | 108 +++++++
 library/ZendAfi/View/Helper/Accueil/Base.php  |   4 +
 .../Chili/Library/ProfilePatcher.php          |   8 +-
 .../Chili/Library/Widget/Login/View.php       |  44 +--
 .../Intonation/Assets/css/intonation.css      |   4 +
 .../Library/FormCustomizer/Menu.php           |   4 +
 .../templates/Intonation/Library/Settings.php |   1 +
 .../Library/View/Wrapper/MenuEntry.php        | 118 ++++++++
 .../Library/Widget/Carousel/Definition.php    |  26 ++
 .../Library/Widget/Carousel/Form.php          |  16 +-
 .../Widget/{ => Carousel}/Menu/Definition.php |  34 ++-
 .../Widget/{ => Carousel}/Menu/Form.php       |  43 +--
 .../Library/Widget/Carousel/Menu/View.php     | 115 ++++++++
 .../Intonation/Library/Widget/Nav/View.php    | 213 +-------------
 .../Intonation/Library/WidgetTemplates.php    |   4 +-
 library/templates/Intonation/Template.php     |   2 +-
 .../Intonation/View/Abstract/Carousel.php     |  13 +
 .../Intonation/View/Menu/Abstract.php         | 147 ++++++++++
 .../Intonation/View/Menu/RenderHorizontal.php |  27 ++
 .../Menu/View.php => View/Menu/RenderNav.php} |  25 +-
 .../Intonation/View/Menu/RenderVertical.php   |  32 +++
 .../Intonation/View/RenderMenuEntry.php       | 140 +++++++++
 .../Muscle/Library/ProfilePatcher.php         |   6 +-
 .../Polygone/Library/ProfilePatcher.php       |  14 +-
 .../TerreDuMilieu/Library/ProfilePatcher.php  |   8 +-
 public/opac/css/core.css                      |  11 +
 .../Activitypub/ActivitypubReviewTest.php     |   4 +-
 .../scenarios/Templates/TemplatesMenuTest.php | 270 +++++++++++++++++-
 tests/scenarios/Templates/TemplatesTest.php   |   4 +-
 .../Templates/TemplatesWidgetInMenusTest.php  |   8 +-
 32 files changed, 1157 insertions(+), 307 deletions(-)
 create mode 100644 VERSIONS_HOTLINE/136539
 create mode 100644 library/templates/Intonation/Library/View/Wrapper/MenuEntry.php
 rename library/templates/Intonation/Library/Widget/{ => Carousel}/Menu/Definition.php (56%)
 rename library/templates/Intonation/Library/Widget/{ => Carousel}/Menu/Form.php (56%)
 create mode 100644 library/templates/Intonation/Library/Widget/Carousel/Menu/View.php
 create mode 100644 library/templates/Intonation/View/Menu/Abstract.php
 create mode 100644 library/templates/Intonation/View/Menu/RenderHorizontal.php
 rename library/templates/Intonation/{Library/Widget/Menu/View.php => View/Menu/RenderNav.php} (55%)
 create mode 100644 library/templates/Intonation/View/Menu/RenderVertical.php
 create mode 100644 library/templates/Intonation/View/RenderMenuEntry.php

diff --git a/VERSIONS_HOTLINE/136539 b/VERSIONS_HOTLINE/136539
new file mode 100644
index 00000000000..b044fa8ccc1
--- /dev/null
+++ b/VERSIONS_HOTLINE/136539
@@ -0,0 +1 @@
+ - ticket #136539 : Magasin de thèmes : Les boites menus partagent maintenant les représentations des listes des boites de type carrousel.
\ No newline at end of file
diff --git a/library/Class/Systeme/Widget/Abstract.php b/library/Class/Systeme/Widget/Abstract.php
index ed39afc2463..d0b4d8df856 100644
--- a/library/Class/Systeme/Widget/Abstract.php
+++ b/library/Class/Systeme/Widget/Abstract.php
@@ -484,4 +484,14 @@ abstract class Class_Systeme_Widget_Abstract extends Class_Entity {
   public function getAvailableOrders() {
     return $this->getFormInstance()->getOrders();
   }
+
+
+  public function setChildren($children) {
+    return $this->set('Children', $children);
+  }
+
+
+  public function getChildren() {
+    return $this->get('Children');
+  }
 }
diff --git a/library/Class/Systeme/Widget/Menu.php b/library/Class/Systeme/Widget/Menu.php
index e7bf95f1b3a..5947f79f478 100644
--- a/library/Class/Systeme/Widget/Menu.php
+++ b/library/Class/Systeme/Widget/Menu.php
@@ -22,6 +22,9 @@
 
 class Class_Systeme_Widget_Menu extends Class_Systeme_Widget_Abstract {
 
+  protected $_sub_menus = [];
+
+
   public static function findAllAsArray($id_profil) {
     if(!$profil = Class_Profil::find($id_profil))
       return [];
@@ -39,6 +42,65 @@ class Class_Systeme_Widget_Menu extends Class_Systeme_Widget_Abstract {
   }
 
 
+  public static function loadFromWidgetPreferences($preferences) {
+    if ( ! isset($preferences['menu']))
+      return null;
+
+    $menu_id = $preferences['menu'];
+
+    if ( '' === $menu_id)
+      return null;
+
+    $profile_id = static::extractProfileIdFromMenu($menu_id);
+    $menu_id = static::extractMenuIdFromMenu($menu_id);
+
+    if( ! $menu = (new Class_Systeme_Widget_Menu)
+       ->setId($menu_id)
+       ->setProfileId($profile_id)
+       ->load())
+      return null;
+
+    return $menu->loadSubMenus();
+  }
+
+
+  public static function extractProfileIdFromMenu($menu) {
+    return static::_extractFromMenuWith($menu,
+                                        function()
+                                        {
+                                          return Class_Profil::getCurrentProfil()->getId();
+                                        },
+
+                                        function($menu_as_array)
+                                        {
+                                          return array_shift($menu_as_array);
+                                        });
+  }
+
+
+  public static function extractMenuIdFromMenu($menu) {
+    return static::_extractFromMenuWith($menu,
+                                        function() use ($menu)
+                                        {
+                                          return $menu;
+                                        },
+
+                                        function($menu_as_array)
+                                        {
+                                          return array_pop($menu_as_array);
+                                        });
+  }
+
+
+  protected static function _extractFromMenuWith($menu, $callback, $callback_with_separator) {
+    if (false === strpos($menu, Class_Systeme_ModulesMenu::MENU_CONFIG_PROFIL_SEPARATOR))
+      return $callback();
+
+    $menu_as_array = explode(Class_Systeme_ModulesMenu::MENU_CONFIG_PROFIL_SEPARATOR, $menu);
+    return $callback_with_separator($menu_as_array);
+  }
+
+
   protected function _load() {
     return $this->_transmute($this->find());
   }
@@ -245,4 +307,50 @@ class Class_Systeme_Widget_Menu extends Class_Systeme_Widget_Abstract {
   public function getLocalSettings() {
     return $this->get('LocalSettings');
   }
+
+
+  public function loadSubMenus() {
+    if ( $this->getSubMenus())
+      return $this;
+
+    $menu_settings = $this->getLocalSettings();
+    if ( ! isset($menu_settings['menus']))
+      return $this;
+
+    foreach ($menu_settings['menus'] as $sub_menu)
+      $this->addSubMenu($sub_menu);
+
+    return $this;
+  }
+
+
+  public function addSubMenu($sub_menu) {
+    if ( ! isset($sub_menu['id_module']))
+      return $this;
+
+    if ( ! $menu = (new Class_Systeme_Widget_Menu)
+        ->setId($sub_menu['id_module'])
+        ->setProfileId($this->getProfileId())
+        ->setParent($this->getId())
+        ->load())
+      return $this;
+
+    $sub_menus = ( $sub_menus = $this->getSubMenus())
+      ? $sub_menus
+      : [];
+
+    $this->_sub_menus [] = $menu;
+    return $this;
+  }
+
+
+  public function setSubMenus($submenus) {
+    $this->_sub_menus = $submenus;
+    return $this;
+  }
+
+
+  public function getSubMenus() {
+    return $this->_sub_menus;
+  }
 }
diff --git a/library/ZendAfi/View/Helper/Accueil/Base.php b/library/ZendAfi/View/Helper/Accueil/Base.php
index 865cd558d85..ab20ad14100 100644
--- a/library/ZendAfi/View/Helper/Accueil/Base.php
+++ b/library/ZendAfi/View/Helper/Accueil/Base.php
@@ -586,4 +586,8 @@ class ZendAfi_View_Helper_Accueil_Base extends ZendAfi_View_Helper_ModuleAbstrac
     return $this->_template_context->isInMenu();
   }
 
+
+  public function getSettings() {
+    return $this->_settings;
+  }
 }
diff --git a/library/templates/Chili/Library/ProfilePatcher.php b/library/templates/Chili/Library/ProfilePatcher.php
index 8701e4c6cc7..8d2f665799b 100644
--- a/library/templates/Chili/Library/ProfilePatcher.php
+++ b/library/templates/Chili/Library/ProfilePatcher.php
@@ -333,7 +333,7 @@ class Chili_Library_ProfilePatcher extends Intonation_Library_ProfilePatcher {
                     $this->_template->withNameSpace('show_content') => 0,
                     $this->_template->withNameSpace('show_footer') => 0])
 
-      ->_addWidget(Intonation_Library_Widget_Menu_Definition::CODE,
+      ->_addWidget(Intonation_Library_Widget_Carousel_Menu_Definition::CODE,
                    Class_Profil::DIV_BANNIERE,
                    ['titre' => $this->_('Menu recherche'),
                     'boite' => ['chili_fixed_top','no_border', 'no_border_radius', 'no_shadow', 'position_fixed_top', 'w-100'],
@@ -490,7 +490,7 @@ class Chili_Library_ProfilePatcher extends Intonation_Library_ProfilePatcher {
                     $this->_template->withNameSpace('show_content') => 0,
                     $this->_template->withNameSpace('show_footer') => 0])
 
-      ->_addWidget(Intonation_Library_Widget_Menu_Definition::CODE,
+      ->_addWidget(Intonation_Library_Widget_Carousel_Menu_Definition::CODE,
                    Class_Profil::DIV_FOOTER,
                    ['titre' => $this->_('Menu réseau'),
                     'boite' => ['no_border', 'no_border_radius', 'no_shadow', 'admin_tools_invert_colors'],
@@ -503,7 +503,7 @@ class Chili_Library_ProfilePatcher extends Intonation_Library_ProfilePatcher {
                     $this->_template->withNameSpace('show_content') => 0,
                     $this->_template->withNameSpace('show_footer') => 0])
 
-      ->_addWidget(Intonation_Library_Widget_Menu_Definition::CODE,
+      ->_addWidget(Intonation_Library_Widget_Carousel_Menu_Definition::CODE,
                    Class_Profil::DIV_FOOTER,
                    ['titre' => $this->_('Plan du site'),
                     'boite' => ['no_border', 'no_border_radius', 'no_shadow', 'admin_tools_invert_colors', 'chili_footer_widget', 'px-3'],
@@ -526,7 +526,7 @@ class Chili_Library_ProfilePatcher extends Intonation_Library_ProfilePatcher {
                     $this->_template->withNameSpace('show_content') => 0,
                     $this->_template->withNameSpace('show_footer') => 0])
 
-      ->_addWidget(Intonation_Library_Widget_Menu_Definition::CODE,
+      ->_addWidget(Intonation_Library_Widget_Carousel_Menu_Definition::CODE,
                    Class_Profil::DIV_FOOTER,
                    ['titre' => $this->_('Mentions légales'),
                     'boite' => ['no_border', 'no_border_radius', 'no_shadow', 'admin_tools_invert_colors', 'chili_footer_widget', 'px-3'],
diff --git a/library/templates/Chili/Library/Widget/Login/View.php b/library/templates/Chili/Library/Widget/Login/View.php
index d1101c0dd36..d9cb4424e08 100644
--- a/library/templates/Chili/Library/Widget/Login/View.php
+++ b/library/templates/Chili/Library/Widget/Login/View.php
@@ -34,34 +34,36 @@ class Chili_Library_Widget_Login_View extends Intonation_Library_Widget_Login_Vi
 
 class ChililLoginRenderToggle extends IntonationLoginRenderToggle {
 
-  protected function _parentRenderLogin() {
-    $form_html = parent::_parentRenderLogin();
-
-    $id_menu = $this->_settings->getNotAuthenticatedMenu();
-    $helper = new Intonation_Library_Widget_Menu_View(1, ['type_module' => 'MENU',
-                                                          'preferences' => ['menu' => $id_menu]]);
-    $helper->setView($this->_view);
-    $menu_html = $helper->getContent();
 
-    return $form_html . $menu_html;
+  protected function _parentRenderLogin() {
+    return
+      parent::_parentRenderLogin()
+      . $this->_withIdMenuRender($this->_settings->getNotAuthenticatedMenu());
   }
 
 
   public function renderLogged() {
-    $id_menu = $this->_settings->getAuthenticatedMenu();
+    $html = $this->_view->tag('h1',
+                              $this->_user->getNomAff(),
+                              ['class' => 'widget-header'])
+      . $this->_withIdMenuRender($this->_settings->getAuthenticatedMenu());
+
+    return
+      $this->_view->renderDropdown($html,
+                                   $this->_view->abonne_Name($this->_user),
+                                   '',
+                                   'dropdown-menu-right');
+  }
 
-    $helper = new Intonation_Library_Widget_Menu_View(1, ['type_module' => 'MENU',
-                                                          'preferences' => ['menu' => $id_menu]]);
-    $helper->setView($this->_view);
 
-    return $this->_view->renderDropdown($this->_view->tag('h1',
-                                                          $this->_user->getNomAff(),
-                                                          ['class' => 'widget-header'])
-                                        .
-                                        $helper->getContent(),
+  protected function _withIdMenuRender($id_menu) {
+    $helper = new Intonation_Library_Widget_Carousel_Menu_View(md5($id_menu),
+                                                               ['type_module' => 'MENU',
+                                                                'preferences' => ['menu' => $id_menu]]);
+    $helper
+      ->setView($this->_view)
+      ->getHtml();
 
-                                        $this->_view->abonne_Name($this->_user),
-                                        '',
-                                        'dropdown-menu-right');
+    return $helper->getContent();
   }
 }
diff --git a/library/templates/Intonation/Assets/css/intonation.css b/library/templates/Intonation/Assets/css/intonation.css
index 1fde9a1bd5c..98636073450 100644
--- a/library/templates/Intonation/Assets/css/intonation.css
+++ b/library/templates/Intonation/Assets/css/intonation.css
@@ -646,6 +646,10 @@ dl.row {
     right: 0 !important;
 }
 
+.card-deck {
+    margin: 0;
+}
+
 .card_grid > .card,
 .card-deck > .card,
 .card-columns > .card {
diff --git a/library/templates/Intonation/Library/FormCustomizer/Menu.php b/library/templates/Intonation/Library/FormCustomizer/Menu.php
index 2667944ffe1..49f8928b400 100644
--- a/library/templates/Intonation/Library/FormCustomizer/Menu.php
+++ b/library/templates/Intonation/Library/FormCustomizer/Menu.php
@@ -22,6 +22,10 @@
 
 class Intonation_Library_FormCustomizer_Menu extends Intonation_Library_FormCustomizer_Abstract {
   public function getForm() {
+    $this->_form->removeElement('order');
+    $this->_form->removeElement('size');
+    $this->_form->removeElement('rendering');
+
     return $this->_addExpandBreakpoint();
   }
 }
diff --git a/library/templates/Intonation/Library/Settings.php b/library/templates/Intonation/Library/Settings.php
index af7da64244f..caaf1339394 100644
--- a/library/templates/Intonation/Library/Settings.php
+++ b/library/templates/Intonation/Library/Settings.php
@@ -305,6 +305,7 @@ class Intonation_Library_Settings extends Intonation_System_Abstract {
                                                   'div class library_horaire' => 'col-12',
                                                   'div class library_opening' => 'col-12',
                                                   'div class library_opening_hours' => 'col-12 d-none',
+                                                  'div class menu_with_carousel_layout' => 'overflow-visible',
                           ],
 
                           'icons_map_doc_types' => [],
diff --git a/library/templates/Intonation/Library/View/Wrapper/MenuEntry.php b/library/templates/Intonation/Library/View/Wrapper/MenuEntry.php
new file mode 100644
index 00000000000..7f887ab0db7
--- /dev/null
+++ b/library/templates/Intonation/Library/View/Wrapper/MenuEntry.php
@@ -0,0 +1,118 @@
+<?php
+/**
+ * Copyright (c) 2012-2021, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class Intonation_Library_View_Wrapper_MenuEntry extends Intonation_Library_View_Wrapper_Abstract {
+
+  public function getMainTitle() {
+    return $this->_model->getLabel();
+  }
+
+  public function getMainLink() {
+    return '';
+  }
+
+
+  public function getEmbedMedia() {
+    return '';
+  }
+
+
+  public function getHtmlPicture() {
+    return '';
+  }
+
+
+  public function getPicture() {
+    return '';
+  }
+
+
+  public function getPictureAction() {
+    return null;
+  }
+
+
+  /** @return string */
+  public function getSecondaryTitle() {
+    return '';
+  }
+
+
+  /** @return Intonation_Library_Link */
+  public function getSecondaryLink() {
+    return null;
+  }
+
+
+  /** @return string icon html */
+  public function getSecondaryIco() {
+    return '';
+  }
+
+
+  /** @return array of Intonation_Library_Link */
+  public function getActions() {
+    return [];
+  }
+
+
+  /** @return string html */
+  public function getDescription() {
+    return '';
+  }
+
+
+  /** @return string html */
+  public function getFullDescription() {
+    return '';
+  }
+
+
+  /** @return string */
+  public function getDescriptionTitle() {
+    return '';
+  }
+
+
+  /** @return html */
+  public function getBadges() {
+    return '';
+  }
+
+
+  /** @return string */
+  public function getDocType() {
+    return '';
+  }
+
+
+  /** @return string */
+  public function getDocTypeLabel() {
+    return '';
+  }
+
+
+  /** @return Intonation_Library_OsmData */
+  public function getOsmData() {
+    return null;
+  }
+}
diff --git a/library/templates/Intonation/Library/Widget/Carousel/Definition.php b/library/templates/Intonation/Library/Widget/Carousel/Definition.php
index f4e025a0a90..a84c7f6ef5e 100644
--- a/library/templates/Intonation/Library/Widget/Carousel/Definition.php
+++ b/library/templates/Intonation/Library/Widget/Carousel/Definition.php
@@ -83,6 +83,32 @@ class Intonation_Library_Widget_Carousel_Definition extends Class_Systeme_Module
                                 'layout' => $layout,
                                 'boite' => $styles_callback()]];
   }
+
+
+  public function getOrderedLayouts($layouts) {
+    asort($layouts, SORT_NATURAL | SORT_FLAG_CASE);
+    return $layouts;
+  }
+
+
+  public function getLayoutsList() {
+    return array_merge([static::ACCORDION_CAROUSEL => $this->_('Accordéon'),
+                        static::CAROUSEL => $this->_('Carousel'),
+                        static::MULTIPLE_CAROUSEL => $this->_('Carousel à 3 colonnes'),
+                        static::MULTIPLE_CAROUSEL_PLUS => $this->_('Carousel à 5 colonnes'),
+                        static::GRID => $this->_('Grille'),
+                        static::HORIZONTAL_LISTING => $this->_('Liste horizontale'),
+                        static::LISTING => $this->_('Liste verticale'),
+                        static::LISTING_WITH_OPTIONS => $this->_('Liste verticale à interactions'),
+                        static::WALL => $this->_('Mur'),
+                        static::MAP => $this->_('Carte géographique')],
+                       Class_Template::current()->getCarouselLayouts());
+  }
+
+
+  public function getLayouts() {
+    return $this->getOrderedLayouts($this->getLayoutsList());
+  }
 }
 
 
diff --git a/library/templates/Intonation/Library/Widget/Carousel/Form.php b/library/templates/Intonation/Library/Widget/Carousel/Form.php
index 828412a86e6..c09a7836af0 100644
--- a/library/templates/Intonation/Library/Widget/Carousel/Form.php
+++ b/library/templates/Intonation/Library/Widget/Carousel/Form.php
@@ -142,21 +142,7 @@ abstract class Intonation_Library_Widget_Carousel_Form extends ZendAfi_Form_Conf
 
 
   public function getLayouts() {
-    $layouts =  array_merge([Intonation_Library_Widget_Carousel_Definition::ACCORDION_CAROUSEL => $this->_('Accordéon'),
-                             Intonation_Library_Widget_Carousel_Definition::CAROUSEL => $this->_('Carousel'),
-                             Intonation_Library_Widget_Carousel_Definition::MULTIPLE_CAROUSEL => $this->_('Carousel à 3 colonnes'),
-                             Intonation_Library_Widget_Carousel_Definition::MULTIPLE_CAROUSEL_PLUS => $this->_('Carousel à 5 colonnes'),
-                             Intonation_Library_Widget_Carousel_Definition::GRID => $this->_('Grille'),
-                             Intonation_Library_Widget_Carousel_Definition::HORIZONTAL_LISTING => $this->_('Liste horizontale'),
-                             Intonation_Library_Widget_Carousel_Definition::LISTING => $this->_('Liste verticale'),
-                             Intonation_Library_Widget_Carousel_Definition::LISTING_WITH_OPTIONS => $this->_('Liste verticale à interactions'),
-                             Intonation_Library_Widget_Carousel_Definition::WALL => $this->_('Mur'),
-                             Intonation_Library_Widget_Carousel_Definition::MAP => $this->_('Carte géographique')],
-                            Class_Template::current()->getCarouselLayouts());
-
-    asort($layouts, SORT_NATURAL | SORT_FLAG_CASE);
-
-    return $layouts;
+    return (new Intonation_Library_Widget_Carousel_Definition)->getLayouts();
   }
 
 
diff --git a/library/templates/Intonation/Library/Widget/Menu/Definition.php b/library/templates/Intonation/Library/Widget/Carousel/Menu/Definition.php
similarity index 56%
rename from library/templates/Intonation/Library/Widget/Menu/Definition.php
rename to library/templates/Intonation/Library/Widget/Carousel/Menu/Definition.php
index 6358ec3534c..2171e8fea2f 100644
--- a/library/templates/Intonation/Library/Widget/Menu/Definition.php
+++ b/library/templates/Intonation/Library/Widget/Carousel/Menu/Definition.php
@@ -20,7 +20,7 @@
  */
 
 
-class Intonation_Library_Widget_Menu_Definition extends Class_Systeme_ModulesAccueil_Null {
+class Intonation_Library_Widget_Carousel_Menu_Definition extends Class_Systeme_ModulesAccueil_Null {
   use Intonation_Library_Widget_TemplatesAwareNoHeader;
 
   const
@@ -32,10 +32,11 @@ class Intonation_Library_Widget_Menu_Definition extends Class_Systeme_ModulesAcc
     $_group = Class_Systeme_ModulesAccueil::GROUP_SITE,
     $_isPhone = false;
 
+
   public function __construct() {
     $this->_libelle = $this->_('Boite menu');
-    $this->_form = 'Intonation_Library_Widget_Menu_Form';
-    $this->_view_helper = 'Intonation_Library_Widget_Menu_View';
+    $this->_form = Intonation_Library_Widget_Carousel_Menu_Form::class;
+    $this->_view_helper = Intonation_Library_Widget_Carousel_Menu_View::class;
     $this->_defaultValues = ['titre' => $this->_libelle,
                              'menu' => '',
                              'layout' => static::LAYOUT_HORIZONTAL,
@@ -46,4 +47,31 @@ class Intonation_Library_Widget_Menu_Definition extends Class_Systeme_ModulesAcc
   protected function _templateTitle() {
     return $this->_('Menu');
   }
+
+
+  public function getMenuLayouts() {
+    return [static::LAYOUT_HORIZONTAL => $this->_('Menu horizontal'),
+            static::LAYOUT_VERTICAL => $this->_('Menu vertical')];
+  }
+
+
+
+  public function getLayoutsList() {
+    $layouts = array_merge((new Intonation_Library_Widget_Carousel_Definition)
+                           ->getLayoutsList(),
+                           $this->getMenuLayouts());
+
+    unset($layouts[Intonation_Library_Widget_Carousel_Definition::ACCORDION_CAROUSEL]);
+    unset($layouts[Intonation_Library_Widget_Carousel_Definition::MAP]);
+    unset($layouts[Intonation_Library_Widget_Carousel_Definition::LISTING]);
+    unset($layouts[Intonation_Library_Widget_Carousel_Definition::LISTING_WITH_OPTIONS]);
+
+    return $layouts;
+  }
+
+
+  public function getLayouts() {
+    return (new Intonation_Library_Widget_Carousel_Definition)
+      ->getOrderedLayouts($this->getLayoutsList());
+  }
 }
\ No newline at end of file
diff --git a/library/templates/Intonation/Library/Widget/Menu/Form.php b/library/templates/Intonation/Library/Widget/Carousel/Menu/Form.php
similarity index 56%
rename from library/templates/Intonation/Library/Widget/Menu/Form.php
rename to library/templates/Intonation/Library/Widget/Carousel/Menu/Form.php
index 491a95ca4d0..f53d2e575d3 100644
--- a/library/templates/Intonation/Library/Widget/Menu/Form.php
+++ b/library/templates/Intonation/Library/Widget/Carousel/Menu/Form.php
@@ -20,25 +20,36 @@
  */
 
 
-class Intonation_Library_Widget_Menu_Form extends ZendAfi_Form_Configuration_Widget_Base {
+class Intonation_Library_Widget_Carousel_Menu_Form extends Intonation_Library_Widget_Carousel_Form {
   public function init() {
     parent::init();
-    $this
-      ->addElement('select',
-                   'menu',
-                   ['label' => $this->_('Menu utilisé'),
-                    'registerInArrayValidator' => false,
-                    'multiOptions' => (new Class_Systeme_ModulesAccueil_MenuVertical())->getMenus()])
-
-      ->addElement('select',
-                   'layout',
-                   ['label' => $this->_('Disposition'),
-                    'multiOptions' => [Intonation_Library_Widget_Menu_Definition::LAYOUT_HORIZONTAL => $this->_('horizontale'),
-                                       Intonation_Library_Widget_Menu_Definition::LAYOUT_VERTICAL => $this->_('verticale')]])
-
-      ->addToSelectionGroup(['menu'])
+    Class_Template::current()->customMenuForm($this);
+  }
+
+
+  public function customPopulate($datas, $form = null) {
+    parent::customPopulate($datas);
+
+    return $this
+      ->replaceWith('data_sources',
+                    ['select',
+                     'menu',
+                     ['label' => $this->_('Menu utilisé'),
+                      'registerInArrayValidator' => false,
+                      'multiOptions' => (new Class_Systeme_ModulesAccueil_MenuVertical())->getMenus()]])
+
       ->addToDisplaySettingsGroup(['layout']);
+  }
 
-    Class_Template::current()->customMenuForm($this);
+
+  public function getOrders() {
+    return [];
+  }
+
+
+  public function getLayouts() {
+    return
+      (new Intonation_Library_Widget_Carousel_Menu_Definition)
+      ->getLayouts();
   }
 }
\ No newline at end of file
diff --git a/library/templates/Intonation/Library/Widget/Carousel/Menu/View.php b/library/templates/Intonation/Library/Widget/Carousel/Menu/View.php
new file mode 100644
index 00000000000..691b097017a
--- /dev/null
+++ b/library/templates/Intonation/Library/Widget/Carousel/Menu/View.php
@@ -0,0 +1,115 @@
+<?php
+/**
+ * Copyright (c) 2012-2021, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class Intonation_Library_Widget_Carousel_Menu_View extends Intonation_Library_Widget_Carousel_View {
+
+
+  protected
+    $_menu,
+    $_use_menu_layout;
+
+
+  protected function _findElements() {
+    return ( $this->_menu = Class_Systeme_Widget_Menu::loadFromWidgetPreferences($this->preferences))
+      ? $this->_menu->getSubMenus()
+      : [];
+  }
+
+
+  protected function _getWrapper() {
+    return Intonation_Library_View_Wrapper_MenuEntry::class;
+  }
+
+
+  protected function _getLinkToAllTitle() {
+    return '';
+  }
+
+
+  protected function _getRSSUrl() {
+    return '';
+  }
+
+
+  protected function _getContentCallback($rendering) {
+    return function ($element)
+      {
+        return $this->view
+          ->renderMenuEntry($element, $this->_useMenuLayout());
+      };
+  }
+
+
+  protected function _getLayoutHelperClass($layout) {
+    if ( Intonation_Library_Widget_Carousel_Menu_Definition::LAYOUT_VERTICAL == $layout)
+      return Intonation_View_Menu_RenderVertical::class;
+
+    if ( Intonation_Library_Widget_Carousel_Menu_Definition::LAYOUT_HORIZONTAL == $layout)
+      return Intonation_View_Menu_RenderHorizontal::class;
+
+    return parent::_getLayoutHelperClass($layout);
+  }
+
+
+  protected function _getLayoutHelper($layout) {
+    $this->_layout_helper = parent::_getLayoutHelper($layout);
+
+    if (in_array($layout,
+                 [Intonation_Library_Widget_Carousel_Menu_Definition::LAYOUT_VERTICAL,
+                  Intonation_Library_Widget_Carousel_Menu_Definition::LAYOUT_HORIZONTAL]))
+      $this->_layout_helper->setCarouselContext($this);
+
+    return $this->_layout_helper;
+  }
+
+
+  protected function _getHTML() {
+    $html = parent::_getHTML();
+
+    return $this->_useMenuLayout()
+      ? $html
+      : $this->_div(['class' => 'menu_with_carousel_layout'],
+                    $html);
+  }
+
+
+  protected function _useMenuLayout() {
+    if ( isset($this->_use_menu_layout))
+      return $this->_use_menu_layout;
+
+    $layout = $this->_settings->getLayout();
+    return $this->_use_menu_layout =
+      array_key_exists($layout,
+                       (new Intonation_Library_Widget_Carousel_Menu_Definition)->getMenuLayouts());
+  }
+
+
+  protected function _extendedActions() {
+    if ( ! $this->_menu)
+      return [];
+
+    return [function()
+            {
+              return $this->view->tagEditMenu($this->_menu->getId(), $this->_menu->getProfileId());
+            }];
+  }
+}
diff --git a/library/templates/Intonation/Library/Widget/Nav/View.php b/library/templates/Intonation/Library/Widget/Nav/View.php
index 540e98a7d93..9a78169e295 100644
--- a/library/templates/Intonation/Library/Widget/Nav/View.php
+++ b/library/templates/Intonation/Library/Widget/Nav/View.php
@@ -20,214 +20,21 @@
  */
 
 
-class Intonation_Library_Widget_Nav_View extends Zendafi_View_Helper_Accueil_Base {
+class Intonation_Library_Widget_Nav_View extends Intonation_Library_Widget_Carousel_Menu_View {
 
-  protected
-    $_profile,
-    $_profile_id,
-    $_menu_id,
-    $_menu;
 
-  public function shouldCacheContent() {
-    return false;
-  }
-
-
-  public function getHtml() {
-    $this->titre = $this->_settings->getTitre();
-  }
-
-
-  public function getContent() {
-    $menu = $this->preferences['menu'];
-
-    list($this->_profile_id, $this->_menu_id) =
-      (false !== strpos($menu, Class_Systeme_ModulesMenu::MENU_CONFIG_PROFIL_SEPARATOR))
-      ? explode(Class_Systeme_ModulesMenu::MENU_CONFIG_PROFIL_SEPARATOR, $menu)
-      : [Class_Profil::getCurrentProfil()->getId(), $menu];
-
-    $this->_profile = Class_Profil::find($this->_profile_id);
-
-    if(!$this->_menu = (new Class_Systeme_Widget_Menu)
-       ->setId($this->_menu_id)
-       ->setProfileId($this->_profile_id)
-       ->load())
-      return '';
-
-    $this->_menu_settings = $this->_menu->getLocalSettings();
-    return $this->contenu = $this->_renderNav();
-  }
-
-
-  protected function _extendedActions() {
-    return [function() { return $this->_editMenu(); }];
-  }
-
-
-  protected function _editMenu() {
-    return $this->view->tagEditMenu($this->_menu_id, $this->_profile_id);
-  }
+  protected function _getLayoutHelper($layout) {
+    $helper_class = $this->_getLayoutHelperClass($layout);
+    $this->_layout_helper = new Intonation_View_Menu_RenderNav;
+        $this->_layout_helper
+      ->setView($this->view)
+      ->setCarouselContext($this);
 
-
-  protected function _renderNav() {
-    return $this->_tag('nav',
-                       $this->_navbarHeader()
-                       . $this->_navBarEntries(),
-                       ['class' => 'navbar ' . $this->_addExpandBreakPoint(),
-                        'role' => 'navigation']);
-  }
-
-
-  protected function _addExpandBreakPoint() {
-    $value = $this->_settings->getExpandBreakpoint();
-
-    if ($value == Intonation_Library_Constants::RESPONSIVE_MODE_ALWAYS)
-      return ' navbar-collapse';
-
-    return ' navbar-expand'
-      . ($value ? '-' . $value : '');
-  }
-
-
-  protected function _navbarHeader() {
-    return $this->_tag('div',
-                       $this->_renderLogo($this->_menu_settings)
-                       . $this->_renderSandwich(),
-                       ['class' => 'd-flex navbar-header']);
+    return $this->_layout_helper;
   }
 
 
-  protected function _renderSandwich() {
-    return $this->_tag('button',
-                       $this->_tag('span', '', ['class' => 'navbar-toggler-icon']),
-                       ['class' => 'navbar-light navbar-toggler collapsed',
-                        'type' => 'button',
-                        'data-toggle' => 'collapse',
-                        'data-target' => '#navbar_' . $this->id_module . '_' . $this->_menu_id]);
-  }
-
-
-  protected function _renderLogo($params) {
-    $thumb = (isset($params['picto']) && $params['picto'])
-      ? $this->_renderPicto($params)
-      : $this->_tag('span', '', ['class' => 'glyphicon glyphicon-globe']);
-
-    return $this->_tag('span',
-                       $thumb . $params['libelle'],
-                       ['class' => 'navbar-brand']);
-  }
-
-
-  protected function _renderPicto($params) {
-    if (!isset($params['picto']))
-      return '';
-
-    if (!$src = $params['picto'])
-      return '';
-
-    if ($src == 'vide.gif')
-      return '';
-
-    $alt = isset($params['picto_alt'])
-      ? $params['picto_alt']
-      : '';
-
-    return Class_Template::current()->getIco($this->view, $src, '', ['alt' => $alt]);
-  }
-
-
-  protected function _navBarEntries() {
-    return $this->_tag('div',
-                       $this->_renderChildren($this->_menu_settings['menus'], $this->_menu_id),
-                       ['class' => 'navbar-collapse collapse',
-                        'id' => 'navbar_' . $this->id_module . '_' . $this->_menu_id]);
-  }
-
-
-  protected function _renderChildren($children, $parent, $dropdown = false) {
-    if(empty($children))
-      return '';
-
-    $html = [];
-    foreach($children as $key => $child) {
-      if(isset($child['id_module']))
-        $key = $child['id_module'];
-
-      if(!$entry = $this->_renderRow($key, $parent))
-        continue;
-
-      if($is_menu = (isset($child['sous_menus']) && (!empty($child['sous_menus']))))
-        $entry .= $this->_renderChildren($child['sous_menus'], $parent, true);
-
-      $html[] = $this->_tag('li', $entry, ['class' => 'nav-item' . ($is_menu ? ' dropdown' : '')]);
-    }
-
-    $classes = $dropdown
-      ? 'dropdown-menu'
-      : 'nav navbar-nav ' . $this->_getLayout();
-
-    return $this->_tag('ul',
-                       implode($html),
-                       ['class' => $classes]);
-  }
-
-
-  protected function _getActiveClass($entry) {
-    return $entry->isActive()
-      ? 'active_item'
-      : '';
-  }
-
-
-  protected function _getLayout() {
-    return '';
-  }
-
-
-  protected function _renderRow($id, $parent, $params = []) {
-    if(!$child = (new Class_Systeme_Widget_Menu)
-       ->setId($id)
-       ->setProfileId($this->_profile_id)
-       ->setParent($parent)
-       ->load())
-      return '';
-
-    $label = $this->_renderPicto($child->getLocalSettings())
-      . $this->_tag('div',
-                    $child->getLabel(),
-                    ['class' => 'button_text d-sm-inline text-left']);
-
-    if ('' !== ($tag = (string) $child->getTag()))
-      $label .= $this->view->tag('span',
-                                 $tag,
-                                 ['class' => 'badge_tag']);
-
-    $html = $this->_getHtml($child, $label);
-
-    return $html
-      . $this->view->tagEditMenu($id, $this->_profile_id, $parent);
-  }
-
-
-  protected function _getHtml($instance, $label) {
-    if ($instance->isMenu())
-      return $this->view->tagAnchor('#',
-                                    $label
-                                    . $this->_tag('span', '', ['class' => 'caret']),
-                                    ['class' => 'nav-link dropdown-toggle',
-                                     'title' => $this->_('Afficher ou masquer le menu "%s"', $instance->getLabel()),
-                                     'data-toggle' => 'dropdown',
-                                     'role' => 'button',
-                                     'aria-haspopup' => 'true',
-                                     'onclick' => 'return false;',
-                                     'aria-expanded' => 'false']);
-
-    if ($instance->isWidget())
-      return Class_Template::current()->renderWidget($instance->beInMenu(), $this->view);
-
-    return $this->view->tagAnchor($instance->getLink(),
-                                  $label,
-                                  ['title' => $this->_('Accéder à "%s"', $instance->getLabel()),
-                                   'class' => 'nav-link ' . $this->_getActiveClass($instance)]);
+  protected function _useMenuLayout() {
+    return true;
   }
 }
diff --git a/library/templates/Intonation/Library/WidgetTemplates.php b/library/templates/Intonation/Library/WidgetTemplates.php
index 80e3b20594e..95d1a4aada7 100644
--- a/library/templates/Intonation/Library/WidgetTemplates.php
+++ b/library/templates/Intonation/Library/WidgetTemplates.php
@@ -280,7 +280,7 @@ class Intonation_Library_WidgetTemplates_Nav
 
   public function _init() {
     $this->_widgets->addAll([new Intonation_Library_Widget_Breadcrumb_Definition,
-                             new Intonation_Library_Widget_Menu_Definition,
+                             new Intonation_Library_Widget_Carousel_Menu_Definition,
                              new Intonation_Library_Widget_Nav_Definition,
                              ]);
   }
@@ -292,7 +292,7 @@ class Intonation_Library_WidgetTemplates_Nav
 
 
   protected function _defaultStyles($code) {
-    $method = in_array($code, [Intonation_Library_Widget_Menu_Definition::CODE,
+    $method = in_array($code, [Intonation_Library_Widget_Carousel_Menu_Definition::CODE,
                                Intonation_Library_Widget_Nav_Definition::CODE])
       ? 'defaultMenuStyles'
       : 'defaultStyles';
diff --git a/library/templates/Intonation/Template.php b/library/templates/Intonation/Template.php
index 2cb5c0169a0..e1c0f5e7fc1 100644
--- a/library/templates/Intonation/Template.php
+++ b/library/templates/Intonation/Template.php
@@ -187,7 +187,7 @@ class Intonation_Template extends Class_Template {
 
        Intonation_Library_Widget_Carousel_Newsletter_Definition::CODE => new Intonation_Library_Widget_Carousel_Newsletter_Definition,
 
-       Intonation_Library_Widget_Menu_Definition::CODE => new Intonation_Library_Widget_Menu_Definition,
+       Intonation_Library_Widget_Carousel_Menu_Definition::CODE => new Intonation_Library_Widget_Carousel_Menu_Definition,
 
        Intonation_Library_Widget_Carousel_Rss_Definition::CODE => new Intonation_Library_Widget_Carousel_Rss_Definition,
 
diff --git a/library/templates/Intonation/View/Abstract/Carousel.php b/library/templates/Intonation/View/Abstract/Carousel.php
index 2a00a7d93fc..05aef6b4d55 100644
--- a/library/templates/Intonation/View/Abstract/Carousel.php
+++ b/library/templates/Intonation/View/Abstract/Carousel.php
@@ -38,6 +38,19 @@ abstract class Intonation_View_Abstract_Carousel extends ZendAfi_View_Helper_Bas
       . $this->_indicators($this->_numberOfPages($collection), $id)
       . $this->_carouselControl($this->_shouldShowControls($collection), $id);
 
+    Class_ScriptLoader::getInstance()
+      ->addJqueryReady(sprintf('
+$("#%1$s").on("slide.bs.carousel",
+              function() {
+                $("#%1$s").closest(".menu_with_carousel_layout.overflow-visible").removeClass("overflow-visible");
+              });
+
+$("#%1$s").on("slid.bs.carousel",
+              function() {
+                $("#%1$s").closest(".menu_with_carousel_layout").addClass("overflow-visible");
+              });',
+                               $id));
+
     return $this->_tag('div',
                        $content,
                        ['id' => $id,
diff --git a/library/templates/Intonation/View/Menu/Abstract.php b/library/templates/Intonation/View/Menu/Abstract.php
new file mode 100644
index 00000000000..6b38f3bca2f
--- /dev/null
+++ b/library/templates/Intonation/View/Menu/Abstract.php
@@ -0,0 +1,147 @@
+<?php
+/**
+ * Copyright (c) 2012-2021, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+abstract class Intonation_View_Menu_Abstract extends ZendAfi_View_Helper_BaseHelper {
+
+
+  protected $_carousel_context,
+    $_settings,
+    $_menu_id,
+    $_profile_id;
+
+
+  public function renderMenu($collection, $callback) {
+    if ($collection->isEmpty())
+      return '';
+
+    if ( ! $this->_initFromContext())
+      return '';
+
+    $html = '';
+    foreach ( $collection as $menu)
+      $html .= $this->_tag('li', $callback($menu), ['class' => 'nav-item']);
+
+    return $this->_renderMenu($this->_navbarHeader() . $this->_navbarEntries($html),
+                              ['class' => implode(' ',
+                                                  ['navbar',
+                                                   $this->_addExpandBreakPoint()])]);
+  }
+
+
+  protected function _renderMenu($content, $attribs) {
+    return $this->_tag('div', $content, $attribs);
+  }
+
+
+  protected function _initFromContext() {
+    if ( ! $this->_carousel_context)
+      return null;
+
+    if ( ! $this->_settings = $this->_carousel_context->getSettings())
+      return null;
+
+    if ( ! $menu_id = $this->_settings->getMenu())
+      return null;
+
+    $this->_profile_id = Class_Systeme_Widget_Menu::extractProfileIdFromMenu($menu_id);
+    $this->_menu_id = Class_Systeme_Widget_Menu::extractMenuIdFromMenu($menu_id);
+
+    return $this;
+  }
+
+
+  protected function _addExpandBreakPoint() {
+    $value = $this->_settings->getExpandBreakpoint();
+
+    if ($value == Intonation_Library_Constants::RESPONSIVE_MODE_ALWAYS)
+      return ' navbar-collapse';
+
+    return ' navbar-expand'
+      . ($value ? '-' . $value : '');
+  }
+
+
+  protected function _navbarHeader() {
+    return $this->_tag('div',
+                       $this->_renderLogo()
+                       . $this->_renderSandwich(),
+                       ['class' => 'd-flex navbar-header']);
+  }
+
+
+  protected function _renderSandwich() {
+    return $this->_tag('button',
+                       $this->_tag('span', '', ['class' => 'navbar-toggler-icon']),
+                       ['class' => 'navbar-light navbar-toggler collapsed',
+                        'type' => 'button',
+                        'data-toggle' => 'collapse',
+                        'data-target' => '#navbar_' . $this->_settings->getIdModule() . '_' . $this->_menu_id]);
+  }
+
+
+  protected function _renderLogo() {
+    $thumb = $this->_settings->getPicto()
+      ? $this->_renderPicto($this->_settings->getLocalSettings())
+      : $this->_tag('span', '', ['class' => 'glyphicon glyphicon-globe']);
+
+    return $this->_tag('span',
+                       $thumb . $this->_settings->getLibelle(),
+                       ['class' => 'navbar-brand']);
+  }
+
+
+  protected function _renderPicto($params) {
+    if (!isset($params['picto']))
+      return '';
+
+    if (!$src = $params['picto'])
+      return '';
+
+    if ($src == 'vide.gif')
+      return '';
+
+    $alt = isset($params['picto_alt'])
+      ? $params['picto_alt']
+      : '';
+
+    return $this->view->templateIco($src, '', ['alt' => $alt]);
+  }
+
+
+  protected function _navBarEntries($html) {
+    return $this->_tag('div',
+                       $this->_tag('ul', $html, ['class' => 'nav navbar-nav' . $this->_getFlexColumn()]),
+                       ['class' => 'navbar-collapse collapse',
+                        'id' => 'navbar_' . $this->_settings->getIdModule() . '_' . $this->_menu_id]);
+  }
+
+
+  protected function _getFlexColumn() {
+    return '';
+  }
+
+
+  public function setCarouselContext($context) {
+    $this->_carousel_context = $context;
+    return $this;
+  }
+}
\ No newline at end of file
diff --git a/library/templates/Intonation/View/Menu/RenderHorizontal.php b/library/templates/Intonation/View/Menu/RenderHorizontal.php
new file mode 100644
index 00000000000..27bf764ab3c
--- /dev/null
+++ b/library/templates/Intonation/View/Menu/RenderHorizontal.php
@@ -0,0 +1,27 @@
+<?php
+/**
+ * Copyright (c) 2012-2021, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class Intonation_View_Menu_RenderHorizontal extends Intonation_View_Menu_Abstract {
+  public function renderHorizontal($collection, $callback) {
+    return parent::renderMenu($collection, $callback);
+  }
+}
diff --git a/library/templates/Intonation/Library/Widget/Menu/View.php b/library/templates/Intonation/View/Menu/RenderNav.php
similarity index 55%
rename from library/templates/Intonation/Library/Widget/Menu/View.php
rename to library/templates/Intonation/View/Menu/RenderNav.php
index 95b7cd7692b..65e41513a6c 100644
--- a/library/templates/Intonation/Library/Widget/Menu/View.php
+++ b/library/templates/Intonation/View/Menu/RenderNav.php
@@ -1,6 +1,6 @@
 <?php
 /**
- * Copyright (c) 2012-2017, Agence Française Informatique (AFI). All rights reserved.
+ * Copyright (c) 2012-2021, Agence Française Informatique (AFI). All rights reserved.
  *
  * BOKEH is free software; you can redistribute it and/or modify
  * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
@@ -20,22 +20,15 @@
  */
 
 
-class Intonation_Library_Widget_Menu_View extends Intonation_Library_Widget_Nav_View {
-
-
-  protected function _renderNav() {
-    return $this->_tag('div',
-                       $this->_navbarHeader()
-                       . $this->_navBarEntries(),
-                       ['class' => implode(' ',
-                                           ['navbar',
-                                            $this->_addExpandBreakPoint()])]);
+class Intonation_View_Menu_RenderNav extends Intonation_View_Menu_Abstract {
+  public function renderNav($collection, $callback) {
+    return parent::renderMenu($collection, $callback);
   }
 
 
-  protected function _getLayout() {
-    return $this->_settings->getLayout() == Intonation_Library_Widget_Menu_Definition::LAYOUT_VERTICAL
-      ? 'flex-column'
-      : '';
+
+  protected function _renderMenu($content, $attribs) {
+    $attribs ['role'] = 'navigation';
+    return $this->_tag('nav', $content, $attribs);
   }
-}
\ No newline at end of file
+}
diff --git a/library/templates/Intonation/View/Menu/RenderVertical.php b/library/templates/Intonation/View/Menu/RenderVertical.php
new file mode 100644
index 00000000000..5666cb7f9c7
--- /dev/null
+++ b/library/templates/Intonation/View/Menu/RenderVertical.php
@@ -0,0 +1,32 @@
+<?php
+/**
+ * Copyright (c) 2012-2021, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class Intonation_View_Menu_RenderVertical extends Intonation_View_Menu_Abstract {
+  public function renderVertical($collection, $callback) {
+    return parent::renderMenu($collection, $callback);
+  }
+
+
+  protected function _getFlexColumn() {
+    return ' flex-column';
+  }
+}
diff --git a/library/templates/Intonation/View/RenderMenuEntry.php b/library/templates/Intonation/View/RenderMenuEntry.php
new file mode 100644
index 00000000000..a70ab34d666
--- /dev/null
+++ b/library/templates/Intonation/View/RenderMenuEntry.php
@@ -0,0 +1,140 @@
+<?php
+/**
+ * Copyright (c) 2012-2021, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class Intonation_View_RenderMenuEntry extends ZendAfi_View_Helper_BaseHelper {
+
+
+  public function renderMenuEntry($menu_entry_wrapper, $use_menu_layout = false) {
+    return $this->_renderMenuEntry($menu_entry_wrapper->getModel(), $use_menu_layout);
+  }
+
+
+  protected function _renderMenuEntry($menu_entry, $use_menu_layout) {
+    $html = '';
+    $id = uniqid();
+
+    if ( $entry = $this->_renderRow($menu_entry, $id))
+      $html .= $entry;
+
+    $entry .= $this->_renderChildren($menu_entry, $id, $use_menu_layout);
+
+    if ($menu_entry->getChildren())
+      $entry = $this->_div(['class' => 'dropdown'],
+                           $entry);
+
+    return $use_menu_layout
+      ? $entry
+      : $this->_div(['class' => 'menu_entry'],
+                    $entry);
+  }
+
+
+  protected function _renderRow($child, $id) {
+    $label = $this->_renderPicto($child->getLocalSettings())
+      . $this->_tag('div',
+                    $child->getLabel(),
+                    ['class' => 'button_text d-sm-inline text-left']);
+
+    if ('' !== ($tag = (string) $child->getTag()))
+      $label .= $this->view->tag('span',
+                                 $tag,
+                                 ['class' => 'badge_tag']);
+
+    $html = $this->_getHtml($child, $label, $id);
+
+    return $html . $this->view->tagEditMenu($child->getId(), $child->getProfileId(), $child->getParent());
+  }
+
+
+  protected function _renderChildren($menu_entry, $id, $use_menu_layout) {
+    if ( ! $children = $menu_entry->getChildren())
+      return '';
+
+    $html = [];
+    foreach($children as $child) {
+      if ( ! $menu_entry_child = (new Class_Systeme_Widget_Menu)
+          ->setId($child->getId())
+          ->setProfileId($menu_entry->getProfileId())
+          ->setParent($menu_entry->getParent())
+          ->load())
+        continue;
+
+      $html[] = $this->_tag('li',
+                            $this->_renderMenuEntry($menu_entry_child, $use_menu_layout),
+                            ['class' => 'nav-item']);
+    }
+
+    return $this->_tag('ul',
+                       implode($html),
+                       ['class' => 'dropdown-menu',
+                        'aria-labelledby' => $id]);
+  }
+
+
+  protected function _renderPicto($params) {
+    if (!isset($params['picto']))
+      return '';
+
+    if (!$src = $params['picto'])
+      return '';
+
+    if ($src == 'vide.gif')
+      return '';
+
+    $alt = isset($params['picto_alt'])
+      ? $params['picto_alt']
+      : '';
+
+    return Class_Template::current()->getIco($this->view, $src, '', ['alt' => $alt]);
+  }
+
+
+  protected function _getHtml($instance, $label, $id) {
+    if ($instance->isMenu())
+      return $this->view->tagAnchor('#',
+                                    $label
+                                    . $this->_tag('span', '', ['class' => 'caret']),
+                                    ['id' => $id,
+                                     'onclick' => 'return false;',
+                                     'class' => 'nav-link dropdown-toggle',
+                                     'title' => $this->_('Afficher ou masquer le menu "%s"', $instance->getLabel()),
+                                     'data-toggle' => 'dropdown',
+                                     'role' => 'button',
+                                     'aria-haspopup' => 'true',
+                                     'aria-expanded' => 'false']);
+
+    if ($instance->isWidget())
+      return Class_Template::current()->renderWidget($instance->beInMenu(), $this->view);
+
+    return $this->view->tagAnchor($instance->getLink(),
+                                  $label,
+                                  ['title' => $this->_('Accéder à "%s"', $instance->getLabel()),
+                                   'class' => 'nav-link ' . $this->_getActiveClass($instance)]);
+  }
+
+
+  protected function _getActiveClass($entry) {
+    return $entry->isActive()
+      ? 'active_item'
+      : '';
+  }
+}
diff --git a/library/templates/Muscle/Library/ProfilePatcher.php b/library/templates/Muscle/Library/ProfilePatcher.php
index 0d41c733edd..78dae09d039 100644
--- a/library/templates/Muscle/Library/ProfilePatcher.php
+++ b/library/templates/Muscle/Library/ProfilePatcher.php
@@ -196,7 +196,7 @@ class Muscle_Library_ProfilePatcher extends Intonation_Library_ProfilePatcher {
     $this
       ->removeWidgets()
 
-      ->_addWidget(Intonation_Library_Widget_Menu_Definition::CODE,
+      ->_addWidget(Intonation_Library_Widget_Carousel_Menu_Definition::CODE,
                    Class_Profil::DIV_BANNIERE,
                    ['titre' => $this->_('Menu réseau sociaux haut'),
                     'boite' => ['no_border', 'no_border_radius', 'no_shadow', 'justify-content-start', 'menu_buttons'],
@@ -207,7 +207,7 @@ class Muscle_Library_ProfilePatcher extends Intonation_Library_ProfilePatcher {
                     $this->_template->withNameSpace('show_content') => 0,
                     $this->_template->withNameSpace('show_footer') => 0])
 
-      ->_addWidget(Intonation_Library_Widget_Menu_Definition::CODE,
+      ->_addWidget(Intonation_Library_Widget_Carousel_Menu_Definition::CODE,
                    Class_Profil::DIV_BANNIERE,
                    ['titre' => $this->_('Menu d\'aide haut'),
                     'boite' => ['no_border', 'no_border_radius', 'no_shadow', 'justify-content-end', 'menu_buttons'],
@@ -358,7 +358,7 @@ class Muscle_Library_ProfilePatcher extends Intonation_Library_ProfilePatcher {
                     $this->_template->withNameSpace('show_content') => 0,
                     $this->_template->withNameSpace('show_footer') => 1])
 
-      ->_addWidget(Intonation_Library_Widget_Menu_Definition::CODE,
+      ->_addWidget(Intonation_Library_Widget_Carousel_Menu_Definition::CODE,
                    Class_Profil::DIV_FOOTER,
                    ['titre' => $this->_('Menu mentions légales'),
                     'boite' => ['no_border', 'no_border_radius', 'no_shadow', 'pt-5', 'text-align-right', 'ml-auto', 'justify-content-end'],
diff --git a/library/templates/Polygone/Library/ProfilePatcher.php b/library/templates/Polygone/Library/ProfilePatcher.php
index 5ae9f8d5a88..399a70cb248 100644
--- a/library/templates/Polygone/Library/ProfilePatcher.php
+++ b/library/templates/Polygone/Library/ProfilePatcher.php
@@ -288,7 +288,7 @@ class Polygone_Library_ProfilePatcher extends Intonation_Library_ProfilePatcher
     $this
       ->removeWidgets()
 
-      ->_addWidget(Intonation_Library_Widget_Menu_Definition::CODE,
+      ->_addWidget(Intonation_Library_Widget_Carousel_Menu_Definition::CODE,
                    Class_Profil::DIV_BANNIERE,
                    ['titre' => $this->_('Menu volant'),
                     'boite' => ['no_border', 'no_border_radius', 'no_shadow', 'flying_widget', 'position_fixed_top_left', 'z_index_11'],
@@ -301,7 +301,7 @@ class Polygone_Library_ProfilePatcher extends Intonation_Library_ProfilePatcher
                     $this->_template->withNameSpace('show_footer') => 0])
 
 
-      ->_addWidget(Intonation_Library_Widget_Menu_Definition::CODE,
+      ->_addWidget(Intonation_Library_Widget_Carousel_Menu_Definition::CODE,
                    Class_Profil::DIV_BANNIERE,
                    ['titre' => $this->_('Menu réseau sociaux haut'),
                     'boite' => ['no_border', 'no_border_radius', 'no_shadow', 'justify-content-start', 'menu_buttons'],
@@ -312,7 +312,7 @@ class Polygone_Library_ProfilePatcher extends Intonation_Library_ProfilePatcher
                     $this->_template->withNameSpace('show_content') => 0,
                     $this->_template->withNameSpace('show_footer') => 0])
 
-      ->_addWidget(Intonation_Library_Widget_Menu_Definition::CODE,
+      ->_addWidget(Intonation_Library_Widget_Carousel_Menu_Definition::CODE,
                    Class_Profil::DIV_BANNIERE,
                    ['titre' => $this->_('Menu d\'aide haut'),
                     'boite' => ['no_border', 'no_border_radius', 'no_shadow', 'justify-content-end', 'menu_buttons'],
@@ -336,7 +336,7 @@ class Polygone_Library_ProfilePatcher extends Intonation_Library_ProfilePatcher
                     $this->_template->withNameSpace('show_content') => 0,
                     $this->_template->withNameSpace('show_footer') => 0])
 
-      ->_addWidget(Intonation_Library_Widget_Menu_Definition::CODE,
+      ->_addWidget(Intonation_Library_Widget_Carousel_Menu_Definition::CODE,
                    Class_Profil::DIV_BANNIERE,
                    ['titre' => $this->_('Menu boite'),
                     'boite' => ['no_border', 'no_border_radius', 'no_shadow', 'justify-content-end'],
@@ -397,7 +397,7 @@ class Polygone_Library_ProfilePatcher extends Intonation_Library_ProfilePatcher
                     $this->_template->withNameSpace('show_content') => 0,
                     $this->_template->withNameSpace('show_footer') => 0])
 
-      ->_addWidget(Intonation_Library_Widget_Menu_Definition::CODE,
+      ->_addWidget(Intonation_Library_Widget_Carousel_Menu_Definition::CODE,
                    Class_Profil::DIV_MAIN,
                    ['titre' => $this->_('Menu des thèmes'),
                     'boite' => ['no_border', 'no_border_radius', 'no_shadow', 'polygone_big_menu_buttons'],
@@ -467,7 +467,7 @@ class Polygone_Library_ProfilePatcher extends Intonation_Library_ProfilePatcher
                     $this->_template->withNameSpace('show_content') => 0,
                     $this->_template->withNameSpace('show_footer') => 0])
 
-      ->_addWidget(Intonation_Library_Widget_Menu_Definition::CODE,
+      ->_addWidget(Intonation_Library_Widget_Carousel_Menu_Definition::CODE,
                    Class_Profil::DIV_FOOTER,
                    ['titre' => $this->_('Menu plan du site'),
                     'boite' => ['no_border', 'no_border_radius', 'no_shadow'],
@@ -481,7 +481,7 @@ class Polygone_Library_ProfilePatcher extends Intonation_Library_ProfilePatcher
                     $this->_template->withNameSpace('show_content') => 0,
                     $this->_template->withNameSpace('show_footer') => 0])
 
-      ->_addWidget(Intonation_Library_Widget_Menu_Definition::CODE,
+      ->_addWidget(Intonation_Library_Widget_Carousel_Menu_Definition::CODE,
                    Class_Profil::DIV_FOOTER,
                    ['titre' => $this->_('Menu mentions légales'),
                     'boite' => ['no_border', 'no_border_radius', 'no_shadow'],
diff --git a/library/templates/TerreDuMilieu/Library/ProfilePatcher.php b/library/templates/TerreDuMilieu/Library/ProfilePatcher.php
index 8b9687e2af3..2b0cd908924 100644
--- a/library/templates/TerreDuMilieu/Library/ProfilePatcher.php
+++ b/library/templates/TerreDuMilieu/Library/ProfilePatcher.php
@@ -254,7 +254,7 @@ class TerreDuMilieu_Library_ProfilePatcher extends Intonation_Library_ProfilePat
     $this
       ->removeWidgets()
 
-      ->_addWidget(Intonation_Library_Widget_Menu_Definition::CODE,
+      ->_addWidget(Intonation_Library_Widget_Carousel_Menu_Definition::CODE,
                    Class_Profil::DIV_BANNIERE,
                    ['titre' => $this->_('Menu réseau sociaux haut'),
                     'boite' => ['no_border', 'no_border_radius', 'no_shadow', 'tdm_social_network_widget', 'tdm_widget'],
@@ -413,7 +413,7 @@ class TerreDuMilieu_Library_ProfilePatcher extends Intonation_Library_ProfilePat
                     $this->_template->withNameSpace('show_content') => 0,
                     $this->_template->withNameSpace('show_footer') => 1])
 
-      ->_addWidget(Intonation_Library_Widget_Menu_Definition::CODE,
+      ->_addWidget(Intonation_Library_Widget_Carousel_Menu_Definition::CODE,
                    Class_Profil::DIV_FOOTER,
                    ['titre' => $this->_('Menu réseau sociaux bas'),
                     'boite' => ['no_border', 'no_border_radius', 'no_shadow', 'tdm_social_network_widget', 'pt-3', 'pb-3', 'admin_tools_invert_colors'],
@@ -444,7 +444,7 @@ class TerreDuMilieu_Library_ProfilePatcher extends Intonation_Library_ProfilePat
                     $this->_template->withNameSpace('show_content') => 0,
                     $this->_template->withNameSpace('show_footer') => 1])
 
-      ->_addWidget(Intonation_Library_Widget_Menu_Definition::CODE,
+      ->_addWidget(Intonation_Library_Widget_Carousel_Menu_Definition::CODE,
                    Class_Profil::DIV_FOOTER,
                    ['titre' => $this->_('Menu crédits'),
                     'boite' => ['no_border', 'no_border_radius', 'no_shadow', 'pt-3', 'pb-3', 'border-primary', 'border-left', 'admin_tools_invert_colors'],
@@ -458,7 +458,7 @@ class TerreDuMilieu_Library_ProfilePatcher extends Intonation_Library_ProfilePat
                     $this->_template->withNameSpace('show_content') => 0,
                     $this->_template->withNameSpace('show_footer') => 0])
 
-      ->_addWidget(Intonation_Library_Widget_Menu_Definition::CODE,
+      ->_addWidget(Intonation_Library_Widget_Carousel_Menu_Definition::CODE,
                    Class_Profil::DIV_BANNIERE,
                    ['titre' => $this->_('Menu volant'),
                     'boite' => ['no_border', 'no_border_radius', 'no_shadow', 'tdm_flying_widget', 'position_fixed_top_right', 'z_index_11'],
diff --git a/public/opac/css/core.css b/public/opac/css/core.css
index 93fb9174cbb..97edcd71ec3 100644
--- a/public/opac/css/core.css
+++ b/public/opac/css/core.css
@@ -2,6 +2,7 @@
     --front-background-modale: rgba(0, 0 , 0 ,0.9);
 }
 
+.menu_entry,
 li.nav-item,
 .widget,
 section_wrapper,
@@ -13,6 +14,16 @@ section {
     position: relative;
 }
 
+.menu_entry .edit_menu {
+    bottom: 0;
+    left: unset !important;
+}
+
+.menu_with_carousel_layout.overflow-visible > *,
+.menu_with_carousel_layout.overflow-visible > * > * {
+    overflow: visible;
+}
+
 .section_content_wrapper .ui-sortable-placeholder {
     visibility: visible !important;
     background-color: var(--front-background-modale) !important;
diff --git a/tests/scenarios/Activitypub/ActivitypubReviewTest.php b/tests/scenarios/Activitypub/ActivitypubReviewTest.php
index 7921d74879f..9fc32e0e38d 100644
--- a/tests/scenarios/Activitypub/ActivitypubReviewTest.php
+++ b/tests/scenarios/Activitypub/ActivitypubReviewTest.php
@@ -1306,11 +1306,11 @@ class ActivitypubReviewTemplateIntonationRecordReviewsTest extends AbstractContr
 
               '26' => ['division' => 2,
                        'type_module' => 'MENU',
-                       'preferences' => ['layout' => Intonation_Library_Widget_Menu_Definition::LAYOUT_VERTICAL]],
+                       'preferences' => ['layout' => Intonation_Library_Widget_Carousel_Menu_Definition::LAYOUT_VERTICAL]],
 
               '27' => ['division' => 4,
                        'type_module' => 'RSS',
-                       'preferences' => ['layout' => Intonation_Library_Widget_Menu_Definition::LAYOUT_VERTICAL]],
+                       'preferences' => ['layout' => Intonation_Library_Widget_Carousel_Menu_Definition::LAYOUT_VERTICAL]],
              ],
 
              'section' => [ '1' => ['boite' => ['ultra_light_widget']]]
diff --git a/tests/scenarios/Templates/TemplatesMenuTest.php b/tests/scenarios/Templates/TemplatesMenuTest.php
index 536cec3a7bf..b2e4e0e3c4a 100644
--- a/tests/scenarios/Templates/TemplatesMenuTest.php
+++ b/tests/scenarios/Templates/TemplatesMenuTest.php
@@ -1,4 +1,4 @@
-<?php
+$<?php
 /**
  * Copyright (c) 2012-2020, Agence Française Informatique (AFI). All rights reserved.
  *
@@ -18,7 +18,7 @@
  * along with BOKEH; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
-require_once 'TemplatesTest.php';
+  require_once 'TemplatesTest.php';
 
 
 class TemplatesMenuDispatchEditMenuHTest extends TemplatesIntonationTestCase {
@@ -125,17 +125,17 @@ class TemplatesMenuUrlEditTest extends TemplatesMenuUrlTestCase {
 
 
 
-class TemplatesMenuDispatchIndexHTest extends TemplatesIntonationTestCase {
+class TemplatesMenuDispatchIndexTest extends TemplatesIntonationTestCase {
 
   public function setUp() {
     parent::setUp();
-    $this->dispatch('/opac/index/index/id_profil/72', true);
+    $this->dispatch('/opac/index/index/id_profil/72');
   }
 
 
   /** @test */
   public function navDropdownAnchorShouldHaveOnclichReturnFalse() {
-    $this->assertXPath('//li[@class="nav-item dropdown"]/a[@onclick="return false;"]');
+    $this->assertXPath('//li[@class="nav-item"]//div[@class="dropdown"]/a[@onclick="return false;"]');
   }
 }
 
@@ -176,7 +176,13 @@ class TemplatesMenuUrlProfilTestCase extends TemplatesIntonationTestCase {
 
   /** @test */
   public function linkToProfilJeunesseShouldNotBeActive() {
-    $this->assertXPath('//a[contains(@href, "/accueil/jeunesse")][not(contains(@class,"active"))]', $this->_response->getBody());
+    $this->assertXPath('//a[contains(@href, "/accueil/jeunesse")][not(contains(@class,"active"))]');
+  }
+
+
+  /** @test */
+  public function totoGifShouldBePresent() {
+    $this->assertXPath('//img[@src="toto.gif"]');
   }
 }
 
@@ -206,7 +212,6 @@ class TemplatesMenuDisplayTwoTimesTest extends Admin_AbstractControllerTestCase
     $this->assertXPathContentContains('//a[@href="/widget/render-menu-entry/id_menu/6/menu_profil/1/id_submenu/3/id_profil/55"]', 'Je veux m\'inscrire');
   }
 
-
   /** @test */
   public function linkToRenderMenuEntryIWantToSubscribeShouldBePresentTwoTime() {
     $this->assertXPathCount('//a[@href="/widget/render-menu-entry/id_menu/6/menu_profil/1/id_submenu/3/id_profil/55"]', 2);
@@ -216,6 +221,91 @@ class TemplatesMenuDisplayTwoTimesTest extends Admin_AbstractControllerTestCase
 
 
 
+class TemplatesMenuEditWidgetTest extends Admin_AbstractControllerTestCase {
+
+  protected $_storm_default_to_volatile = true;
+
+
+  public function setUp() {
+    parent::setUp();
+
+    $profile = $this->_buildTemplateProfil(['id' => 4]);
+
+    (new Class_Template_ProfilePatcher(Class_Template::current()))
+      ->setProfile($profile)
+      ->addWidget(Intonation_Library_Widget_Carousel_Menu_Definition::CODE,
+                  Class_Profil::DIV_MAIN,
+                  []);
+
+    $profile->assertSave();
+
+    $this->dispatch('/admin/widget/edit-widget/id/1/id_profil/4');
+  }
+
+
+  /** @test */
+  public function formShouldContainsLayoutCarousel() {
+    $this->assertXPath('//select/option[@value="carousel"]');
+  }
+
+
+  /** @test */
+  public function orderShouldNotBePresent() {
+    $this->assertNotXPath('//select[@name="order"]');
+  }
+
+
+  /** @test */
+  public function sizeShouldNotBePresent() {
+    $this->assertNotXPath('//input[@name="size"]');
+  }
+
+
+  /** @test */
+  public function renderingShouldNotBePresent() {
+    $this->assertNotXPath('//select[@name="rendering"]');
+  }
+}
+
+
+
+class TemplatesMenuEditWidgetPostTest extends Admin_AbstractControllerTestCase {
+
+  protected $_storm_default_to_volatile = true;
+
+
+  public function setUp() {
+    parent::setUp();
+
+    $profile = $this->_buildTemplateProfil(['id' => 4]);
+
+    (new Class_Template_ProfilePatcher(Class_Template::current()))
+      ->setProfile($profile)
+      ->addWidget(Intonation_Library_Widget_Carousel_Menu_Definition::CODE,
+                  Class_Profil::DIV_MAIN,
+                  []);
+
+    $profile->assertSave();
+
+    $this->postDispatch('/admin/widget/edit-widget/id/1/id_profil/4',
+                        ['layout' => 'wall']);
+  }
+
+
+  /** @test */
+  public function layoutOfMenuWidgetShouldBeWall() {
+    Class_Systeme_Widget_Widget::reset();
+    $this->assertEquals('wall', (new Class_Systeme_Widget_Widget)
+                        ->setId(1)
+                        ->setProfileId(4)
+                        ->load()
+                        ->getLocalSettings()['layout']);
+  }
+}
+
+
+
+
 class TemplatesMenuDefaultSettingsTest extends Admin_AbstractControllerTestCase {
 
   protected $_storm_default_to_volatile = true;
@@ -238,3 +328,169 @@ class TemplatesMenuDefaultSettingsTest extends Admin_AbstractControllerTestCase
     $this->assertNotXPathContentContains('//main//div', 'Boite d\'articles');
   }
 }
+
+
+
+class TemplatesMenuTestCase extends AbstractControllerTestCase {
+
+  protected
+    $_storm_default_to_volatile = true,
+    $_menu_id;
+
+
+  public function setUp() {
+    parent::setUp();
+
+    $disk = $this
+      ->mock()
+      ->whenCalled('filesAt')
+      ->answers([])
+
+      ->whenCalled('directoryAt')
+      ->answers(null)
+
+      ->whenCalled('fileAt')
+      ->answers(null);
+
+    Class_FileManager::setFileSystem($disk);
+
+    $profile = $this->_buildTemplateProfil(['id' => 7]);
+
+    $valleiry = $this->fixture(Class_Bib::class,
+                               ['id' => 1,
+                                'libelle' => 'Valleiry']);
+
+    $this->_menu_id = $profile
+      ->addMenu(['libelle' => 'Menu TemplateMenu',
+                 'picto' => '',
+                 'menus' => [
+                             ['type_menu' => 'ACCUEIL',
+                              'libelle' => 'Home',
+                              'use_profil' => 7],
+
+                             ['type_menu' => 'MENU',
+                              'libelle' => 'Libraries',
+                              'sous_menus' => [['type_menu' => 'URL',
+                                                'libelle' => 'Valleiry',
+                                                'url' => $valleiry]]],
+
+                             ['type_menu' => 'URL',
+                              'libelle' => 'Calendar',
+                              'url' => ''],
+
+                             ['type_menu' => 'URL',
+                              'libelle' => 'News',
+                              'url' => ''],
+
+                             ['type_menu' => 'URL',
+                              'libelle' => 'Last reviews',
+                              'url' => ''],
+
+                             ['type_menu' => 'ABON_FICHE',
+                              'libelle' => 'My account']]
+                 ]);
+
+    $profile_patcher = (new Class_Template_ProfilePatcher(Class_Template::current()))
+      ->setProfile($profile)
+      ->addWidget(Intonation_Library_Widget_Nav_Definition::CODE,
+                  Class_Profil::DIV_BANNIERE,
+                  ['menu' => '7-' . $this->_menu_id]);
+  }
+}
+
+
+
+
+class TemplatesMenuLayoutsTest extends TemplatesMenuTestCase {
+
+
+  public function widgetMenu() {
+    return [
+            ['carousel',
+             '//footer//div[@class="menu_with_carousel_layout overflow-visible"]//div[contains(@class, "carousel")]//div[@class="menu_entry"]',
+             'Home'],
+
+            ['carousel',
+             '//script',
+             '.on("slide.bs.carousel",'],
+
+            ['carousel',
+             '//script',
+             '.on("slid.bs.carousel",'],
+
+            ['vertical',
+             '//footer//ul[@class="nav navbar-nav flex-column list-unstyled"]//li',
+             'Home'],
+
+            ['horizontal',
+             '//footer//ul[@class="nav navbar-nav list-unstyled"]//li',
+             'Home']
+    ];
+  }
+
+  /**
+   * @dataProvider widgetMenu
+   * @test */
+  public function withLayoutMenuEntryShouldBeRenderAsExpected($layout, $xpath, $content) {
+    (new Class_Template_ProfilePatcher(Class_Template::current()))
+      ->setProfile(Class_Profil::find(7))
+      ->addWidget(Intonation_Library_Widget_Carousel_Menu_Definition::CODE,
+                  Class_Profil::DIV_FOOTER,
+                  ['layout' => $layout,
+                   'libelle' => 'menu carousel',
+                   'menu' => '7-' . $this->_menu_id]);
+
+    $this->dispatch('/index');
+
+    $this->assertXPathContentContains($xpath, $content, $this->_response->getBody());
+  }
+}
+
+
+
+
+class TemplatesMenuWithMainMenuInFooterTest extends TemplatesMenuTestCase {
+  protected $_storm_default_to_volatile = true;
+
+
+  public function setUp() {
+    parent::setUp();
+
+    $profile_patcher = (new Class_Template_ProfilePatcher(Class_Template::current()))
+      ->setProfile(Class_Profil::find(7))
+      ->addWidget(Intonation_Library_Widget_Carousel_Menu_Definition::CODE,
+                  Class_Profil::DIV_FOOTER,
+                  ['layout' => 'horizontal',
+                   'menu' => '7-' . $this->_menu_id]);
+
+    $this->dispatch('/index');
+  }
+
+  /** @test */
+  public function mainNavShouldHave6NavItems() {
+    $this->assertXPathCount('//header//ul[@class="nav navbar-nav list-unstyled"]/li', 6);
+  }
+}
+
+
+
+
+class TemplatesMenuNavWithLayoutTest extends TemplatesMenuTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    $profile_patcher = (new Class_Template_ProfilePatcher(Class_Template::current()))
+      ->setProfile(Class_Profil::find(7))
+      ->addWidget(Intonation_Library_Widget_Nav_Definition::CODE,
+                  Class_Profil::DIV_FOOTER,
+                  ['layout' => 'grid',
+                   'menu' => '7-' . $this->_menu_id]);
+
+    $this->dispatch('/index');
+  }
+
+  /** @test */
+  public function homeShouldBeRenderInANavTag() {
+    $this->assertXPathContentContains('//footer//nav', 'Home');
+  }
+}
\ No newline at end of file
diff --git a/tests/scenarios/Templates/TemplatesTest.php b/tests/scenarios/Templates/TemplatesTest.php
index 096287b4b81..0757a6fbea7 100644
--- a/tests/scenarios/Templates/TemplatesTest.php
+++ b/tests/scenarios/Templates/TemplatesTest.php
@@ -367,11 +367,11 @@ abstract class TemplatesIntonationTestCase extends TemplatesEnabledTestCase {
 
               '26' => ['division' => 2,
                        'type_module' => 'MENU',
-                       'preferences' => ['layout' => Intonation_Library_Widget_Menu_Definition::LAYOUT_VERTICAL]],
+                       'preferences' => ['layout' => Intonation_Library_Widget_Carousel_Menu_Definition::LAYOUT_VERTICAL]],
 
               '27' => ['division' => 4,
                        'type_module' => 'RSS',
-                       'preferences' => ['layout' => Intonation_Library_Widget_Menu_Definition::LAYOUT_VERTICAL]],
+                       'preferences' => ['layout' => Intonation_Library_Widget_Carousel_Menu_Definition::LAYOUT_VERTICAL]],
              ],
 
              'section' => [ '1' => ['boite' => ['ultra_light_widget']]]
diff --git a/tests/scenarios/Templates/TemplatesWidgetInMenusTest.php b/tests/scenarios/Templates/TemplatesWidgetInMenusTest.php
index 3fe689d77ff..5ec8b26d39e 100644
--- a/tests/scenarios/Templates/TemplatesWidgetInMenusTest.php
+++ b/tests/scenarios/Templates/TemplatesWidgetInMenusTest.php
@@ -81,7 +81,9 @@ class TemplatesWidgetLoginInMenuAndInHeaderTest extends Admin_AbstractController
                                                  'picto' => 'connect.png']]]])
          ->setBoiteOfTypeInDivision(4,
                                     'NAV',
-                                    ['menu' => '1-8'])
+                                    ['picto' => 'logo.gif',
+                                     'libelle' => 'Contact',
+                                     'menu' => '1-8'])
          ->setBoiteOfTypeInDivision(4,
                                     'LOGIN',
                                     ['lien_deconnection' => 'hasta la vista',
@@ -166,4 +168,8 @@ class TemplatesWidgetLoginInMenuAndInHeaderTest extends Admin_AbstractController
   }
 
 
+  /** @test */
+  public function logoGifShouldBePresent() {
+    $this->assertXPath('//img[@src="logo.gif"]');
+  }
 }
-- 
GitLab